Ship Spawning
-
Unless the bug is more subtle than it would appear,
__asm { lea eax, f2 push eax lea eax, f1 push eax lea eax, iRep push eax mov eax, [pub::Reputation::Alloc] call eax add esp, 0Ch }
can now be replaced by
pub::Reputation::Alloc(iRep, f1, f2);
I’m assuming that Hook and the headers have matured enough now for the old bug requiring this workaround to disappear.
I’ll try finding out how to make the AI work…
-
Well this took a while, but here’s a partial dump of the Personality struct:
all values are 32-bit floats unless unknown or otherwise specified f = 32-bit float i = 32-bit unsigned int d = 64-bit double boolean = 1 byte boolean with 3 bytes of padding after, unless otherwise specified (padding is always after) evade_activate_range evade_dodge_style_weight (waggle) evade_dodge_style_weight (waggle_random) evade_dodge_style_weight (slide) evade_dodge_style_weight (corkscrew) evade_dodge_cone_angle (radians) evade_dodge_cone_angle_variance_percent evade_dodge_waggle_axis_cone_angle (radians) evade_dodge_roll_angle (radians) evade_dodge_interval_time evade_dodge_interval_time_variance_percent evade_dodge_distance evade_dodge_time evade_dodge_slide_throttle evade_dodge_turn_throttle evade_dodge_corkscrew_turn_throttle evade_dodge_corkscrew_roll_throttle evade_dodge_corkscrew_roll_flip_direction (bool, 0x01009A1C) evade_dodge_direction_weight (left) evade_dodge_direction_weight (right) evade_dodge_direction_weight (up) evade_dodge_direction_weight (down) evade_break_time evade_break_interval_time evade_break_afterburner_delay evade_break_afterburner_delay_variance_percent evade_break_direction_weight (left) evade_break_direction_weight (right) evade_break_direction_weight (up) evade_break_direction_weight (down) evade_break_roll_throttle evade_break_turn_throttle evade_break_style_weight (sideways) evade_break_style_weight (outrun) evade_break_style_weight (reverse) evade_break_attempt_reverse_time evade_break_reverse_distance buzz_max_time_to_head_away buzz_head_toward_style_weight (straight_to) buzz_head_toward_style_weight (slide) buzz_head_toward_style_weight (waggle) buzz_min_distance_to_head_toward buzz_min_distance_to_head_toward_variance_percent buzz_head_toward_engine_throttle buzz_head_toward_turn_throttle buzz_head_toward_roll_throttle buzz_head_toward_roll_flip_direction (boolean) buzz_dodge_direction_weight (left) buzz_dodge_direction_weight (right) buzz_dodge_direction_weight (up) buzz_dodge_direction_weight (down) buzz_dodge_turn_throttle buzz_dodge_cone_angle (radians) buzz_dodge_cone_angle_variance_percent buzz_dodge_waggle_axis_cone_angle buzz_dodge_roll_angle buzz_dodge_interval_time buzz_dodge_interval_time_variance_percent buzz_slide_throttle buzz_slide_interval_time buzz_slide_interval_time_variance_percent buzz_pass_by_style_weight (straight_by) buzz_pass_by_style_weight (break_away) buzz_pass_by_style_weight (engine_kill) buzz_distance_to_pass_by buzz_pass_by_time buzz_drop_bomb_on_pass_by (boolean) buzz_break_direction_weight (left) buzz_break_direction_weight (right) buzz_break_direction_weight (up) buzz_break_direction_weight (down) buzz_break_direction_cone_angle buzz_break_turn_throttle buzz_pass_by_roll_throttle trail_lock_cone_angle (radians) trail_break_time trail_min_no_lock_time trail_break_roll_throttle trail_break_afterburner (boolean) trail_max_turn_throttle trail_distance strafe_run_away_distance strafe_attack_throttle strafe_turn_throttle engine_kill_search_time engine_kill_face_time engine_kill_use_afterburner (boolean, uses 1f for true and 0f for false) engine_kill_afterburner_time engine_kill_max_target_distance use_shield_repair_pre_delay use_shield_repair_at_damage_percent use_shield_repair_post_delay use_hull_repair_pre_delay use_hull_repair_at_damage_percent use_hull_repair_post_delay fire_style (MULTIPLE = 0i, SINGLE = 1i) gun_fire_interval_time gun_fire_interval_variance_percent gun_fire_burst_interval_time gun_fire_burst_interval_variance_percent gun_fire_no_burst_interval_time gun_fire_accuracy_cone_angle (radians) gun_fire_accuracy_power gun_fire_accuracy_power_npc gun_range_threshold gun_range_threshold_variance_percent gun_target_point_switch_time auto_turret_interval_time auto_turret_burst_interval_time auto_turret_no_burst_interval_time auto_turret_burst_interval_variance_percent mine_launch_interval mine_launch_cone_angle (radians) mine_launch_range missile_launch_range missile_launch_allow_out_of_range (boolean) missile_launch_interval_time missile_launch_interval_variance_percent missile_launch_cone_angle anti_cruise_missile_min_distance anti_cruise_missile_max_distance anti_cruise_missile_pre_fire_delay anti_cruise_missile_interval_time evade_break_damage_trigger_percent evade_dodge_more_damage_trigger_percent drop_mines_damage_trigger_percent drop_mines_damage_trigger_time engine_kill_face_damage_trigger_percent engine_kill_face_damage_trigger_time roll_damage_trigger_percent roll_damage_trigger_time afterburner_damage_trigger_percent afterburner_damage_trigger_time brake_reverse_damage_trigger_percent fire_missiles_damage_trigger_percent fire_missiles_damage_trigger_time fire_guns_damage_trigger_percent fire_guns_damage_trigger_time fire_missiles_damage_trigger_percent fire_missiles_damage_trigger_time evade_break_missile_reaction_time evade_slide_missile_reaction_time evade_afterburn_missile_reaction_time 0x23D (?, 1 byte) evade_missile_distance (f?) countermeasure_active_time countermeasure_unactive_time force_attack_formation_active_time force_attack_formation_unactive_time break_formation_damage_trigger_percent 0x256 break_formation_damage_trigger_time 0x25A break_apart_formation_damage_trigger_percent 0x25E break_apart_formation_damage_trigger_time 0x262 break_formation_missile_reaction_time 0x266 break_apart_formation_missile_reaction_time 0x26A break_apart_formation_on_buzz_head_toward (boolean) 0x26E break_formation_on_buzz_head_toward_time 0x272 regroup_formation_on_buzz_head_toward (boolean, 1 byte) 0x273 break_apart_formation_on_buzz_pass_by (boolean, 3 bytes) 0x276 break_formation_on_buzz_pass_by_time 0x27A regroup_formation_on_buzz_pass_by (boolean, 1 byte) 0x27B break_apart_formation_on_evade_dodge (boolean, 3 bytes) 0x27E break_formation_on_evade_dodge_time 0x282 regroup_formation_on_evade_dodge (boolean, 1 byte) 0x283 break_apart_formation_on_evade_break (boolean, 3 bytes) 0x286 break_formation_on_evade_break_time 0x28A = regroup_formation_on_evade_break (boolean) 0x28E = formation_exit_mode (1 byte + 3 bytes padding afterwards) ..........BREAK_AWAY_FROM_CENTER = 0x00 ..........BREAK_AWAY_FROM_CENTER_AFTERBURNER = 0x01 ..........BRAKE_REVERSE = 0x02 ..........OUTRUN = 0x03 ..........NOTHING = 0x05 formation_exit_top_turn_break_away_throttle formation_exit_roll_outrun_throttle formation_exit_max_time 0x29E (-256i?) 0x2A2 (0i?) 0x2A6 (3i?) 0x2AA (2i?) 0x2AE (10000f?) attack_preference format ... type id (i) ............................ range (f) ............................ flag (15i for guns | TORPEDO | guided | unguided) ................. 0x2B2 fighter (type 0i) ................. 0x2BE freighter (type 1i) ................. 0x2CA transport (type 2i) ................. 0x2D6 gunboat (type 3i) ................. 0x2E2 cruiser (type 4i) ................. 0x2EE capital (type 5i) ................. 0x2FA weapons_platform (type 8i) 0x306 1 byte (0x0D) ................. 0x307 solar (type 10i) ................. 0x313 anything (type 11i) ................. 0x31F other? (type 12i) (could it be that the ID is after? IE fighter = 1i and anything = 12i?) 0x327 (?) 0x32B (1f?) 0x32F (?) 0x333 (?) 0x337 (?) 0x33B (1f?) 0x33F (?) 0x343 (?) 0x347 (?) 0x34B (1f?) 0x34F (6i?) 0x353 (7i?) 0x357 (?) 0x35B (?) 0x35F (?) 0x363 (?) 0x367 (?) 0x36B (?) 0x36F (1f?) 0x373 maximum_leader_target_distance 0x377 (3i?) 0x37B (4i?) 0x37F (4i?) 0x383 (?) 0x387 (?) 0x38B (?) 0x38F (1f?) 0x393 (?) 0x397 (?) 0x39B (?) 0x39F (?) 0x3A3 (35f?) 0x3A7 (55f?) 0x3AB (?) 0x3AF (?) 0x3B3 (? changes) 0x3B7 (? changes) 0x3BB (? changes) 0x3BF (?) 0x3C3 (? changes) 0x3C7 (4i?) 0x3CB flee_when_hull_damaged_percent 0x3CF (?) 0x3D3 (-1f?) 0x3D7 (?) ---------------------- MISSING VALUES FROM CONTENT.DLL leader_makes_me_tougher allow_player_targeting force_attack_formation field_targeting attack_subtarget_order attack_order combat_drift_distance wait_for_leader_target target_toughness_preference scene_toughness_threshold flee_no_weapons_style flee_when_leader_flees_style flee_scene_threat_style loot_flee_threshold loot_preference
I know there are still some holes, particularly near the end; I haven’t completely finished my parsing at that point. All the names are taken directly from pilots_population.ini. All the values are inserted directly, one after the other. Some values appear to have non-zero defaults that are automatically plugged in if the value does not appear in the pilot.
Edit: Now adding the Personality dump I used to figure it out (all values are custom though). Here’s the Hook code I used to dump the Personality:
FILE* fNPCOut; void CmdTargetAI(CCmds* classptr, wstring wszCharname) { uint iClientID = HkGetClientIdFromCharname(wszCharname); uint iShip = 0; pub::Player::GetShip(iClientID, iShip); uint iTarget = 0; pub::SpaceObj::GetTarget(iShip, iTarget); uint iDunno; IObjInspectImpl *inspect; GetShipInspect(iTarget, inspect, iDunno); const CObject* obj = inspect->cobject(); CEqObj* eobj = (CEqObj*) obj; IBehaviorManager* ibm = eobj->get_behavior_interface(); pub::AI::Personality p; ibm->get_personality(p); savedPersonality = p; SYSTEMTIME st; GetSystemTime(&st); string date = "flhook_logs\\npcs\\ai_export_" + itos((int) st.wDay) + "-" + itos((int) st.wMonth) + "-" + itos((int) st.wYear) + "_" + itos((int) st.wHour) + "-" + itos((int) st.wMinute) + "-" + itos((int) st.wSecond) + ".bin"; fopen_s(&fNPCOut, date.c_str(), "w"); fwrite(savedPersonality.data, 2048, 1, fNPCOut); fclose(fNPCOut); }
Just call the command as you would for any other admin command. Must be called by a player, probably not crash/bugproof.
Edit 2: Also attached the INI blocks that generated the dump in the same ZIP.
-
Looking at the disassembly where it reads it from the ini, I can tell you a few things. But first, yes, bool is a single byte, dword-alignment adds the three unused bytes.
0x8c = evade_break_attempt_reverse_time 0x90 = evade_break_reverse_distance 0xB8 = buzz_head_toward_roll_flip_direction (boolean) engine_kill_use_afterburner - read as boolean (TRUE or FALSE), stored as float (1.0f or 0.0f) fire_style - MULTIPLE = 0i, SINGLE (or anything) = 1i 0x1D8 = missile_launch_cone_angle 0x1DC = anti_cruise_missile_min_distance 0x1E0 = anti_cruise_missile_max_distance 0x1E4 = anti_cruise_missile_pre_fire_delay 0x1E8 = anti_cruise_missile_interval_time 0x228 = fire_missiles_damage_trigger_percent 0x22C = fire_missiles_damage_trigger_time 0x258 = break_apart_formation_damage_trigger_percent 0x25C = break_apart_formation_damage_trigger_time 0x268 = break_apart_formation_on_buzz_head_toward (bool) 0x26C = break_formation_on_buzz_head_toward_time 0x270 = regroup_formation_on_buzz_head_toward (bool) 0x271 = break_apart_formation_on_buzz_pass_by (bool) 0x274 = break_formation_on_buzz_pass_by_time 0x278 = regroup_formation_on_buzz_pass_by (bool) 0x279 = break_apart_formation_on_evade_dodge (bool) 0x27C = break_formation_on_evade_dodge_time 0x280 = regroup_formation_on_evade_dodge (bool) 0x281 = break_apart_formation_on_evade_break (bool) 0x288 = regroup_formation_on_evade_break (bool) 0x28C = formation_exit_mode . BREAK_AWAY_FROM_CENTER = 0 . BREAK_AWAY_FROM_CENTER_AFTERBURNER = 1 . BRAKE_REVERSE = 2 . OUTRUN = 3 . NOTHING = 5
-
Thanks adoxa! It appears we’re missing an entire block then, as cruise disruptors are mentioned nowhere in the INIs. This might explain why NPCs rarely if ever fire them by default.
-
Updated dump with adoxa’s values and fleshed out all the missing values.
-
This is slightly off-topic, but the anti_cruise_missile values are indeed not used in the normal pilots_population.ini, and they don’t seem to actually do much (as in, anything). In vanilla FL, AI only used CDs if their target was cruising - this could be worked around by setting auto_turret to true for the Cruise Disruptor weapons, in which case AI will fire them whenever ready to. In fact, setting any missile-based weapon to auto_turret = true will ignore all missile usage parameters defined under the pilot, instead firing missiles when in the weapon’s range.
Edit: As an aside, setting all missile weapons to auto_turret = true may fix RandomMission NPCs not firing missiles. -
Well I tried but I’m a bit stuck here, the rest of the Personality struct seems to consist of a lot of boolean values which are hard to track down, plus many constants that I do not know.
There’s so little left to reverse, think about what could be done with this! Could someone help out?
-
Awesome as usual
-
Seems like the SetPersonalityParams struct is used to initialize the AI properly… And it’s not reversed at all
I’ll try to get on it but I’m not promising anything.
-
enum OP_TYPE; class BaseOp { public: BaseOp( OP_TYPE ); BaseOp( const BaseOp& ); virtual bool validate(); OP_TYPE op_type; int x08; }; struct SetPersonalityParams { SetPersonalityParams(); SetPersonalityParams( const SetPersonalityParams& ); virtual bool validate(); BaseOp baseop; // 0 int state_graph; // -1, used by validate void* x10; // -1 void* x14; // -1 bool state_id; // true - state_graph_id, false - state_graph Personality personality; };
-
Mumbles about not even being allowed to have a go and fail
I kid, nice job adoxa, this’ll help tremendously.
-
Well spawning ships is rock solid now, can spawn any ship with a custom AI at will.
What I’m trying to figure out now are, in order:
-How to stop the NPC from dropping all of its equipment on death
-How to control the NPC
-How to add the NPC to the “normal” NPC list so that it disappears automatically when out of rangeI’m pretty sure the 1st point’s key is in the ShipInfo struct, while the second point obviously lies in the reversal of the Op structs.
I wish I knew just how you can find things out so fast, adoxa
-
Yeah, me too. How is that even possible? Maybe I’m just not knowledgeable enough in the subject matter but I wasn’t aware that you could even pull something so detailed (especially with regard to names of things) from just assembly level de-compilation.
-
Defining those structures was relatively trivial, due to exported constructors. Doing the points above will take a bit more effort, which I’ll not do (not for a while, anyway, by which time I’ll have forgotten). I expect point two can be solved by set_current_directive, so I suppose you want IDirectiveInfo and the remaining Ops?
-
I’m not asking you to do them at all, you’re a busy man with a lot on your plate
What I wondered is whether you could give some insight on how to do what you do. A tutorial if you want, just some tips otherwise. I’d like to stop having to ask you or w0d all the time, but I’ve never gleaned much from disassemblies
-
Well, if you want to give it a go, here’s an example using DirectiveFollowOp.
* Exported function Common.??0DirectiveFollowOp@AI@pub@@QAE@XZ | =1<===== 062DAF30 8BC1 mov eax, ecx 062DAF32 33C9 xor ecx, ecx 062DAF34 C7400404000000 mov dword[eax+04], 00000004 062DAF3B 894808 mov [eax+08], ecx 062DAF3E C7001CE13906 mov dword[eax], Common.??_7DirectiveFollowOp@AI@pub@@6B@ 062DAF44 89480C mov [eax+0C], ecx 062DAF47 C7401000001643 mov dword[eax+10], 43160000 ; 150 062DAF4E 894814 mov [eax+14], ecx 062DAF51 894818 mov [eax+18], ecx 062DAF54 89481C mov [eax+1C], ecx 062DAF57 C740200000C843 mov dword[eax+20], 43C80000 ; 400 062DAF5E C3 ret ```I have a separate program to demangle names, which gives us the namespace and constructor. [eax] is the virtual function table, [eax+04] & [eax+08] are from BaseOp. Looking at Follow in the mission files and where it reads it in content.dll (6F27400), +0C would be the nickname, but I still don't know what the other values are, apart from being float (probably maximum distance, x,y,z offset and something else). So assuming that, we can say:
namespace pub
{
namespace AI
{
enum OP_TYPE { FollowOp = 4 }; // and the others, of courseclass DirectiveFollowOp : public BaseOp // inheritance based on observation { public: DirectiveFollowOp()/* : BaseOp( FollowOp )*/; // some more functions here, should be in the CoreSDK virtual bool validate(); UINT leader; // 0 float max_distance; // 150 Vector ofs; // 0, 0, 0; copy constructor indicates Vector float unknown; // 400 };
}
} -
Very nice, that should help a lot!
Can I know what program(s)/steps you use to get the output you’ve shown? I think I read you use Ollydbg to disassemble the DLLs, but how do you get the values like 150 or 400? From the INIs, from a debug process?