class_name CombatantFactory extends RefCounted var _occupied_ids: Dictionary = {} var _next_suffix_by_prefix: Dictionary = {} func reset_allocator() -> void: _occupied_ids.clear() _next_suffix_by_prefix.clear() func restore_allocator(records: Array[CombatantStateRecord]) -> void: reset_allocator() for record in records: if record != null: reserve_id(record.get_combatant_id()) func reserve_id(combatant_id: StringName) -> bool: if combatant_id.is_empty() or _occupied_ids.has(combatant_id): return false _occupied_ids[combatant_id] = true _observe_numeric_suffix(combatant_id) return true func create_enemy(enemy_definition_id: StringName, position: Vector3) -> CombatantStateRecord: return create_from_definition(SimulationEnemies.get_enemy(enemy_definition_id), position) func create_from_definition(definition: EnemyDefinition, position: Vector3) -> CombatantStateRecord: if definition == null or not definition.validate().is_empty() or not position.is_finite(): return null var combatant_id := _allocate_id(definition.combatant_id_prefix) return CombatantStateRecord.create( combatant_id, definition.kind, definition.faction_id, definition.display_name, position, definition.health, definition.weapon_id, CombatantStateRecord.NO_NPC_ID, definition.hostile, definition.enemy_id ) func get_next_suffix(prefix: String) -> int: return int(_next_suffix_by_prefix.get(prefix, 0)) func is_reserved(combatant_id: StringName) -> bool: return _occupied_ids.has(combatant_id) func _allocate_id(prefix: String) -> StringName: var suffix := get_next_suffix(prefix) var candidate := StringName("%s_%d" % [prefix, suffix]) while _occupied_ids.has(candidate): suffix += 1 candidate = StringName("%s_%d" % [prefix, suffix]) _occupied_ids[candidate] = true _next_suffix_by_prefix[prefix] = suffix + 1 return candidate func _observe_numeric_suffix(combatant_id: StringName) -> void: var text_id := String(combatant_id) var separator_index := text_id.rfind("_") if separator_index <= 0 or separator_index >= text_id.length() - 1: return var suffix_text := text_id.substr(separator_index + 1) if not suffix_text.is_valid_int(): return var suffix := int(suffix_text) if suffix < 0: return var prefix := text_id.substr(0, separator_index) _next_suffix_by_prefix[prefix] = maxi(get_next_suffix(prefix), suffix + 1)