diff --git a/src/SB/Core/x/containers.h b/src/SB/Core/x/containers.h index a2a467a95..6c0a53bc7 100644 --- a/src/SB/Core/x/containers.h +++ b/src/SB/Core/x/containers.h @@ -63,11 +63,23 @@ template struct static_queue return this; } + iterator operator-=(S32 value) + { + return *(*this += -value); + } + iterator* operator++() { *this += 1; return this; } + + iterator operator-(S32 value) const + { + iterator it = *this; + it -= value; + return it; + } }; bool empty() const @@ -82,16 +94,18 @@ template struct static_queue void init(u32 size) { - U32 unk_r3 = 0; + U32 shift = 0; + + size += 1; while (size > 1) { size /= 2; - unk_r3++; + shift++; } - _max_size = unk_r3; - _max_size_mask = 1 << _max_size; - _buffer = (T*)xMemAlloc(gActiveHeap, sizeof(T) * (size), 0); + _max_size = 1 << shift; + _max_size_mask = _max_size - 1; + _buffer = (T*)xMemAlloc(gActiveHeap, sizeof(T) * _max_size, 0); clear(); } @@ -107,11 +121,22 @@ template struct static_queue return *it; } + T& back() + { + iterator it = end() - 1; + return *it; + } + iterator begin() const { return create_iterator(_first); } + iterator end() const + { + return create_iterator(mod_max_size(_first + _size)); + } + iterator create_iterator(u32 initial_size) const { iterator it; @@ -153,6 +178,7 @@ template struct static_queue { U32 orig_size = _size; U32 orig_first = _first; + _first = other._it; _size = mod_max_size((orig_first + orig_size) - _first); } @@ -161,12 +187,6 @@ template struct static_queue _size -= mod_max_size(other._it - it._it); } } - - iterator end() const - { - iterator it; - return create_iterator(mod_max_size(_first + _size)); - } }; template struct fixed_queue diff --git a/src/SB/Core/x/xCollide.h b/src/SB/Core/x/xCollide.h index 3805878bd..3841c8493 100644 --- a/src/SB/Core/x/xCollide.h +++ b/src/SB/Core/x/xCollide.h @@ -140,6 +140,12 @@ bool xSphereHitsOBB(const xSphere&, const xBox&, const xMat4x3&); bool xSphereHitsSphere(const xVec3&, F32, const xVec3&, F32); bool xSphereHitsVCylinder(const xVec3& sc, F32 sr, const xVec3& cc, F32 cr, F32 ch); bool xSphereHitsVCircle(const xSphere& s, const xVec3& c, F32 r); +bool xSphereHitsCapsule(const xVec3& sc, F32 sr, const xVec3& c1, const xVec3& c2, F32 cr); + +inline bool xSphereHitsCapsule(const xSphere& s, const xVec3& c1, const xVec3& c2, F32 cr) +{ + return xSphereHitsCapsule(s.center, s.r, c1, c2, cr); +} bool xSphereHitsVCircle(const xVec3& sc, F32 sr, const xVec3& cc, F32 cr); U32 xSphereHitsModel(const xSphere* b, const xModelInstance* m, xCollis* coll); diff --git a/src/SB/Core/x/xFX.h b/src/SB/Core/x/xFX.h index 009740fb8..83a3f76a0 100644 --- a/src/SB/Core/x/xFX.h +++ b/src/SB/Core/x/xFX.h @@ -71,7 +71,17 @@ struct xFXRibbon U32 mlife; void init(const char*, const char*); - void init(S32, const char*); + + void init(S32, const char* name) + { + init(name, NULL); + } + + void clear() + { + joints.clear(); + } + void set_texture(const char* name); void set_texture(U32); void set_texture(RwTexture* texture); diff --git a/src/SB/Core/x/xMath2.h b/src/SB/Core/x/xMath2.h index 66ac08cae..e04c73827 100644 --- a/src/SB/Core/x/xMath2.h +++ b/src/SB/Core/x/xMath2.h @@ -48,6 +48,16 @@ struct xVec2 F32 x; F32 y; + static xVec2 create(F32 x, F32 y) + { + xVec2 v = { 0.0f, 0.0f }; + + v.x = x; + v.y = y; + + return v; + } + xVec2& assign(F32 xy) { return assign(xy, xy); @@ -75,6 +85,13 @@ struct xVec2 xVec2& operator=(F32); xVec2 operator*(F32) const; xVec2 operator/=(F32); + xVec2 operator+(const xVec2& v) const + { + xVec2 sum = *this; + sum += v; + return sum; + } + xVec2& operator+=(const xVec2&); xVec2& operator*=(F32); xVec2& operator-=(const xVec2&); diff --git a/src/SB/Core/x/xParEmitter.h b/src/SB/Core/x/xParEmitter.h index 50d6b8f7b..5014f1139 100644 --- a/src/SB/Core/x/xParEmitter.h +++ b/src/SB/Core/x/xParEmitter.h @@ -36,7 +36,13 @@ struct xParInterp F32 oofreq; void set(F32, F32, F32, U32); - void set(F32); // Used in zNPCDutchman + void set(F32 v) + { + val[1] = v; + val[0] = v; + interp = 0; + freq = oofreq = 1.0f; + } void order(); void operator=(const xParInterp& p); }; diff --git a/src/SB/Core/x/xSnd.h b/src/SB/Core/x/xSnd.h index b1d06586e..993e561ac 100644 --- a/src/SB/Core/x/xSnd.h +++ b/src/SB/Core/x/xSnd.h @@ -205,6 +205,9 @@ U32 xSndPlay3D(U32 id, F32 vol, F32 pitch, U32 priority, U32 flags, xEnt* parent F32 outerRadius, sound_category category, F32 delay); U32 xSndPlay3D(U32 id, F32 vol, F32 pitch, U32 priority, U32 flags, const xVec3* pos, F32 innerRadius, F32 outerRadius, sound_category category, F32 delay); +U32 xSndPlay3DFade(U32 id, F32 vol, F32 pitch, U32 priority, U32 flags, const xVec3* pos, + F32 innerRadius, F32 outerRadius, sound_category category, F32 fadeTime, + F32 delay); U32 xSndPlayInternal(U32 id, F32 vol, F32 pitch, U32 priority, U32 flags, U32 parentID, xEnt* parentEnt, const xVec3* pos, F32 innerRadius, F32 outerRadius, sound_category category, F32 delay); diff --git a/src/SB/Game/zNPCTypeDutchman.cpp b/src/SB/Game/zNPCTypeDutchman.cpp index 3f22c47d9..fdd3ff02b 100644 --- a/src/SB/Game/zNPCTypeDutchman.cpp +++ b/src/SB/Game/zNPCTypeDutchman.cpp @@ -1,15 +1,15 @@ #include "xVec3.h" #include "xMath3.h" +#include "xMathInlines.h" #include "xDebug.h" +#include "xMemMgr.h" +#include "iCamera.h" #include "zGlobals.h" #include "zNPCTypeDutchman.h" #include -#define f1605 0.0f -#define f1606 1.0f -#define f1689 0.2f -#define f1690 0.1f +#define FRAME_TIME (1.0f / 60.0f) #define ANIM_Idle01 1 #define ANIM_Fidget01 4 //0x10 @@ -32,11 +32,11 @@ #define SOUND_BIZARRE 4 #define SOUND_MORE_BIZARRE 5 -static U32 dutchman_count; - namespace { + F32 angle_difference(const xVec2&, const xVec2&); + struct sound_property { F32 volume; @@ -106,7 +106,7 @@ namespace } flame; struct { - signed int dummy; + S32 dummy; } fly; struct { @@ -159,7 +159,11 @@ namespace tweak_callback cb_sound; void register_tweaks(bool init, xModelAssetParam* ap, U32 apsize, const char*); - void load(xModelAssetParam*, U32); + + void load(xModelAssetParam* ap, U32 apsize) + { + register_tweaks(true, ap, apsize, NULL); + } }; struct sound_data_type @@ -170,23 +174,12 @@ namespace struct sound_asset { - char* name; + const char* name; U32 priority; U32 flags; }; - class curve_node - { - F32 time; - iColor_tag color; - F32 scale; - }; - - static sound_data_type sound_data[6]; - static sound_asset sound_assets[6]; - static xBinaryCamera boss_cam; - static xFXRibbon eye_scorch[2]; - static curve_node burn_ribbon_curve[7]; + static tweak_group tweak; static zParEmitter* plasma_emitter; static xParEmitterCustomSettings plasma_emitter_settings; static zParEmitter* spark_emitter; @@ -206,97 +199,549 @@ namespace static xParEmitterCustomSettings slime_emitter_settings; static zParEmitter* hand_trail_emitter; static zParEmitter* blob_emitter; - static delay_goal sequence[3][16]; + static xFXRibbon eye_scorch[2]; + static sound_data_type sound_data[6]; + static xBinaryCamera boss_cam = { { { 6.0f, 3.0f, 2.0f }, + { 0.2f, 2.2f, -1.0f }, + { 1.0f, 0.2f, 1.5f }, + 10.0f, + 10.0f, + 10.0f, + 10.0f, + 30.0f, + -0.17453292f } }; + + static const delay_goal sequence[3][16] = { { { NPC_GOAL_DUTCHMANIDLE, 1.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANDISAPPEAR, 0.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANIDLE, 1.0f }, + { NPC_GOAL_DUTCHMANFLAME, 0.0f }, + { NPC_GOAL_DUTCHMANIDLE, 0.1f }, + { NPC_GOAL_DUTCHMANPOSTFLAME, 0.0f }, + { 0, -1.0f } }, + { { NPC_GOAL_DUTCHMANIDLE, 1.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANDISAPPEAR, 0.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANDISAPPEAR, 0.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANIDLE, 1.0f }, + { NPC_GOAL_DUTCHMANFLAME, 0.0f }, + { NPC_GOAL_DUTCHMANIDLE, 0.1f }, + { NPC_GOAL_DUTCHMANPOSTFLAME, 0.0f }, + { 0, -1.0f } }, + { { NPC_GOAL_DUTCHMANIDLE, 1.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANDISAPPEAR, 0.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANDISAPPEAR, 0.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANDISAPPEAR, 0.0f }, + { NPC_GOAL_DUTCHMANBEAM, 0.0f }, + { NPC_GOAL_DUTCHMANIDLE, 1.0f }, + { NPC_GOAL_DUTCHMANFLAME, 0.0f }, + { NPC_GOAL_DUTCHMANIDLE, 0.1f }, + { NPC_GOAL_DUTCHMANPOSTFLAME, 0.0f }, + { 0, -1.0f } } }; + + static const xFXRibbon::curve_node burn_ribbon_curve[7] = { + { 0.0f, { 255, 255, 255, 255 }, 0.4f }, { 0.05f, { 255, 255, 155, 255 }, 0.2f }, + { 0.15f, { 205, 155, 55, 255 }, 0.2f }, { 0.3f, { 155, 55, 0, 255 }, 0.2f }, + { 0.45f, { 55, 0, 0, 255 }, 0.2f }, { 0.65f, { 0, 0, 0, 255 }, 0.4f }, + { 1.0f, { 0, 0, 0, 0 }, 0.6f } + }; - static void init_sound() - { - U32 total; + static const sound_asset sound_assets[6] = { + { "FD_eyebeam_loop", 0, 1 }, { "FD_flame_loop", 0, 1 }, { "FD_vapor_loop", 0, 1 }, + { "FD_float_loop", 0, 1 }, { "FD_gas", 0, 0 }, { "FD_revert", 0, 0 } + }; - memset(&sound_data, 0, 0x30); + F32 look_at(xMat3x3& mat, const xVec3& at) + { + F32 mag = at.length(); - for (U32 i = 0; i < 6; i++) + if (mag >= -0.00001f && mag <= 0.00001f) { - xStrHash((const char*)&sound_assets[i]); + mat = g_I3; + return 0.0f; } - } - - U32 play_sound(S32, const xVec3*, F32) - { - return 0; - } - - void kill_sound(S32 a, U32 b) - { - } - static tweak_group tweak; + mat.at = at; + mat.at *= 1.0f / mag; - static void set_volume(S32 which, U32, F32 new_vol) - { - } + F32 ax = xabs(mat.at.x); + F32 ay = xabs(mat.at.y); + F32 az = xabs(mat.at.z); - void tweak_group::register_tweaks(bool init, xModelAssetParam* ap, U32 apsize, const char*) - { - if (init) + if (ax < ay && ax < az) { - orbit_radius = 13.0f; - auto_tweak::load_param(orbit_radius, 1.0f, 0.01f, 50.0f, ap, apsize, - "orbit_radius"); + mat.right.assign(0.0f, mat.at.z, -mat.at.y); } - if (init) + else if (ay < az) { - accel = xVec3::create(1.0f, 3.0f, 2.5f); - auto_tweak::load_param(accel, 0, 0, 0, ap, apsize, "accel"); + mat.right.assign(-mat.at.z, 0.0f, mat.at.x); } - if (init) + else { - max_vel = xVec3::create(1.0f, 3.0f, 2.5f); - auto_tweak::load_param(max_vel, 0, 0, 0, ap, apsize, "max_vel"); + mat.right.assign(mat.at.y, -mat.at.x, 0.0f); } - if (init) + + mat.right.normalize(); + mat.up = mat.right.cross(mat.at); + + return mag; + } + + static void init_sound() + { + memset(sound_data, 0, sizeof(sound_data)); + + for (S32 i = 0; i < 6; i++) { - turn_accel = 540.0f; - auto_tweak::load_param(turn_accel, DEG2RAD(10), 0.01f, 1000000000.0f, ap, - apsize, "turn_accel"); + sound_data[i].id = xStrHash(sound_assets[i].name); } - if (init) + } + + U32 play_sound(S32 which, const xVec3* loc, F32 volume) + { + const sound_asset& asset = sound_assets[which]; + const sound_data_type& data = sound_data[which]; + const sound_property& prop = tweak.sound[which]; + + if (asset.flags & 1) { - turn_max_vel = 180.0f; - auto_tweak::load_param(turn_max_vel, DEG2RAD(10), 0.01f, 1000000000.0f, ap, - apsize, "turn_max_vel"); + return xSndPlay3DFade(data.id, volume * prop.volume, 1.0f, asset.priority, 0x800, loc, + prop.range_inner, prop.range_outer, SND_CAT_GAME, 0.0f, + prop.delay); } - if (init) + + return xSndPlay3D(data.id, volume * prop.volume, 1.0f, asset.priority, 0x800, loc, + prop.range_inner, prop.range_outer, SND_CAT_GAME, prop.delay); + } + + void kill_sound(S32 which, U32 handle) + { + const sound_asset& asset = sound_assets[which]; + const sound_property& prop = tweak.sound[which]; + + if (asset.flags & 1) { - ground_y = -1.4f; - auto_tweak::load_param(ground_y, 1.0f, -1000000000.0f, 1000000000.0f, ap, - apsize, "ground_y"); + xSndStopFade(handle, prop.fade_time); } - if (init) + else { - ground_radius = 12.0f; - auto_tweak::load_param(ground_radius, 1.0f, 0.0f, 1000000000.0f, ap, apsize, - "ground_radius"); + xSndStop(handle); } - if (init) + } + + static void set_volume(S32 which, U32 handle, F32 new_vol) + { + xSndSetVol(handle, tweak.sound[which].volume * new_vol); + } +} // namespace + +//13 new states +//8 new transitions +xAnimTable* ZNPC_AnimTable_Dutchman() +{ + // clang-format off + S32 ourAnims[13] = { + ANIM_Idle01, + ANIM_Death01, + ANIM_Fidget01, + ANIM_Fidget02, + ANIM_Fidget03, + ANIM_AttackWindup01, + ANIM_AttackLoop01, + ANIM_AttackEnd01, + ANIM_Attack02Windup01, + ANIM_Attack02Loop01, + ANIM_Attack02End01, + ANIM_LassoGrab01, + }; + // clang-format on + xAnimTable* table = xAnimTableNew("zNPCDutchman", NULL, 0); + + xAnimTableNewState(table, g_strz_subbanim[ANIM_Idle01], 0x10, 0, 1.0f, NULL, NULL, 0.0f, NULL, + NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Death01], 0, 0, 1.0f, NULL, NULL, 0.0f, NULL, + NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Fidget01], 0x20, 0, 1.0f, NULL, NULL, 0.0f, NULL, + NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Fidget02], 0x20, 0, 1.0f, NULL, NULL, 0.0f, NULL, + NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Fidget03], 0x20, 0, 1.0f, NULL, NULL, 0.0f, NULL, + NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Taunt01], 0x20, 0, 1.0f, NULL, NULL, 0.0f, NULL, + NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_AttackWindup01], 0x20, 0, 1.0f, NULL, NULL, 0.0f, + NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_AttackLoop01], 0x10, 0, 1.0f, NULL, NULL, 0.0f, + NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_AttackEnd01], 0x20, 0, 1.0f, NULL, NULL, 0.0f, + NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Attack02Windup01], 0x20, 0, 1.0f, NULL, NULL, + 0.0f, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Attack02Loop01], 0x10, 0, 1.0f, NULL, NULL, 0.0f, + NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_Attack02End01], 0x20, 0, 1.0f, NULL, NULL, 0.0f, + NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + xAnimTableNewState(table, g_strz_subbanim[ANIM_LassoGrab01], 0x20, 0x2000000, 1.0f, NULL, NULL, + 0.0f, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + + NPCC_BuildStandardAnimTran(table, g_strz_subbanim, ourAnims, 1, 0.2f); + + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_AttackWindup01], + g_strz_subbanim[ANIM_AttackLoop01], 0, 0, 0x10, 0, 0, 0, 0, 0, 0.1f, 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_AttackLoop01], + g_strz_subbanim[ANIM_AttackEnd01], 0, 0, 0, 0, 0, 0, 0, 0, 0.1f, 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Attack02Windup01], + g_strz_subbanim[ANIM_Attack02Loop01], 0, 0, 0x10, 0, 0, 0, 0, 0, 0.1f, + 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Attack02Loop01], + g_strz_subbanim[ANIM_Attack02End01], 0, 0, 0, 0, 0, 0, 0, 0, 0.1f, 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Fidget02], g_strz_subbanim[ANIM_Idle01], 0, + 0, 0x10, 0, 0, 0, 0, 0, 0.1f, 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Fidget02], + g_strz_subbanim[ANIM_AttackWindup01], 0, 0, 0, 0, 0, 0, 0, 0, 0.1f, 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Fidget02], g_strz_subbanim[ANIM_Fidget01], + 0, 0, 0, 0, 0, 0, 0, 0, 0.1f, 0); + xAnimTableNewTransition(table, g_strz_subbanim[ANIM_LassoGrab01], g_strz_subbanim[ANIM_Death01], + 0, 0, 0, 0, 0, 0, 0, 0, 0.1f, 0); + + return table; +} + +zNPCDutchman* dutchman_reticle_ent; +xVec3 dutchman_reticle_center; +F32 dutchman_reticle_radius; + +static U32 dutchman_count; + +zNPCDutchman::zNPCDutchman(S32 myType) : zNPCSubBoss(myType) +{ + memset(&flag, 0, sizeof(flag)); + dutchman_reticle_ent = this; +} + +void zNPCDutchman::Init(xEntAsset* asset) +{ + dutchman_count++; + + boss_cam.init(); + + zNPCCommon::Init(asset); + + flg_move = 1; + flg_vuln = 1; + + const char* scorch_name[2] = { "Dutchman Left Burn", "Dutchman Right Burn" }; + + for (S32 i = 0; i < 2; i++) + { + eye_scorch[i].init(0x1ff, scorch_name[i]); + eye_scorch[i].set_default_config(); + eye_scorch[i].set_curve(burn_ribbon_curve, 7); + eye_scorch[i].set_texture("fx_streak1"); + eye_scorch[i].cfg.life_time = 5.0f; + eye_scorch[i].refresh_config(); + } + + RwTexture* laser_texture = (RwTexture*)xSTFindAsset(xStrHash("laser_beam_white_blue"), NULL); + + laser_raster = laser_texture->raster; + + waves.init(15); + slime.slices.init(63); + + init_sound(); + + S32 model_index = 0; + xModelInstance* m = model; + + while (m != NULL) + { + if (model_index == 1) { - alpha = 1.0f; - auto_tweak::load_param(alpha, 1.0f, 0.0f, 1.0f, ap, apsize, "alpha"); + m->Flags |= 0x8000; + break; } - if (init) + + m = m->Next; + model_index++; + } +} + +void zNPCDutchman::Setup() +{ + zNPCSubBoss::Setup(); +} + +void zNPCDutchman::Reset() +{ + zNPCCommon::Reset(); + memset(&flag, 0, 16); + decompose(); + life = 3; + round = 0; + stage = -1; + alpha = 1.0f; + update_round(); + face_player(); + flg_vuln = 1; + reset_speed(); + move.vel = 0.0f; + move.dest = get_center(); + flag.move = MOVE_FOLLOW; + flames.imax_dist = 1.0f / tweak.ground_radius; + reset_blob_mat(); + waves.clear(); + slime.slices.clear(); + eye_scorch[0].clear(); + eye_scorch[1].clear(); + fade.sound_handle = 0; + vanish(); + refresh_reticle(); + flag.fighting = false; + plasma_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_PLASMA"); + plasma_emitter_settings.custom_flags = 0x100; + plasma_emitter_settings.pos = g_O3; + spark_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_SPARKS"); + spark_emitter_settings.custom_flags = 0x100; + spark_emitter_settings.pos = g_O3; + light_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_LIGHT"); + light_emitter_settings.custom_flags = 0x110; + light_emitter_settings.pos = g_O3; + light_emitter_settings.rate.set((2.0f / FRAME_TIME) * tweak.beam.light_rate); + eyeglow_emitter[0] = zParEmitterFind("PAREMIT_DUTCHMAN_EYEGLOW0"); + eyeglow_emitter[1] = zParEmitterFind("PAREMIT_DUTCHMAN_EYEGLOW1"); + death_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_DEATH"); + dissolve_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_DISSOLVE"); + fadeout_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_FADEOUT"); + fadein_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_FADEIN"); + flame_emitter[0] = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_LIGHT"); + flame_emitter[1] = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_NORMAL"); + flame_emitter[2] = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_SPRAY"); + flame_emitter_settings.custom_flags = 0x110; + flame_emitter_settings.pos = g_O3; + flame_emitter_settings.rate.set(1.0f / FRAME_TIME); + snot_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_SNOT"); + snot_emitter_settings.custom_flags = 0x300; + slime_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_SLIME_TRAIL"); + slime_emitter_settings.custom_flags = 0x100; + hand_trail_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_HAND_TRAIL"); + blob_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_BLOB"); + psy_instinct->GoalSet(NPC_GOAL_DUTCHMANNIL, 1); +} + +void zNPCDutchman::Destroy() +{ + zNPCCommon::Destroy(); + dutchman_count--; +} + +void zNPCDutchman::Process(xScene* xscn, F32 dt) +{ + if (!flag.fighting) + { + zNPCCommon::Process(xscn, dt); + } + else + { + delay += dt; + psy_instinct->Timestep(dt, NULL); + if (!flag.fighting) { - speed_mult[0] = 1.0f; - auto_tweak::load_param(speed_mult[0], 1.0f, 0.01f, 10.0f, ap, apsize, - "speed_mult[0]"); + zNPCCommon::Process(xscn, dt); } - if (init) + else { - speed_mult[1] = 1.5f; - auto_tweak::load_param(speed_mult[1], 1.0f, 0.01f, 10.0f, ap, apsize, - "speed_mult[1]"); + if (flag.face_player) + { + const xVec3& player_loc = (xVec3&)globals.player.ent.model->Mat->pos; + const xVec3& center = get_center(); + + turn.dir.assign(player_loc.x - center.x, player_loc.z - center.z); + turn.dir.normalize(); + } + update_turn(dt); + update_move(dt); + update_animation(dt); + update_flames(dt); + update_eye_glow(dt); + update_hand_trail(dt); + update_fade(dt); + update_slime(dt); + + if (check_player_damage()) + { + zEntPlayer_Damage((xBase*)this, 1); + } + update_camera(dt); + refresh_reticle(); + flg_xtrarend |= 1; + zNPCCommon::Process(xscn, dt); } - if (init) - { - speed_mult[2] = 2.0f; + } +} + +S32 zNPCDutchman::SysEvent(xBase* from, xBase* to, U32 toEvent, const F32* toParam, + xBase* toParamWidget, S32* handled) +{ + switch (toEvent) + { + case eEventNPCFightOn: + start_fight(); + break; + case eEventNPCSetActiveOff: + psy_instinct->GoalSet(NPC_GOAL_DUTCHMANDEATH, 1); + break; + default: + *handled = 0; + return zNPCCommon::SysEvent(from, to, toEvent, toParam, toParamWidget, handled); + } + + return 1; +} + +void zNPCDutchman::Render() +{ + zNPCDutchman::render_debug(); +} + +void zNPCDutchman::RenderExtra() +{ + S32 oldzwrite; + S32 oldztest; + S32 oldsrcblend; + S32 olddestblend; + + RwRenderStateGet(rwRENDERSTATEZWRITEENABLE, &oldzwrite); + RwRenderStateGet(rwRENDERSTATEZTESTENABLE, &oldztest); + RwRenderStateGet(rwRENDERSTATESRCBLEND, &oldsrcblend); + RwRenderStateGet(rwRENDERSTATEDESTBLEND, &olddestblend); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + iDrawSetFBMSK(0xffffffff); + + for (xModelInstance* m = model; m != NULL; m = m->Next) + { + xModelRenderSingle(m); + } + + iDrawSetFBMSK(0); + + for (xModelInstance* m = model; m != NULL; m = m->Next) + { + xModelRenderSingle(m); + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + U8 oldcmp = FALSE; + + if (flag.beaming && (beam[0].segments + beam[1].segments) != 0) + { + oldcmp = TRUE; + } + + U8 haloing = flag.fade != FADE_TELEPORT; + + if (oldcmp) + { + render_beam(); + } + + if (haloing) + { + render_halo(); + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)oldzwrite); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)oldztest); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)oldsrcblend); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)olddestblend); +} + +void zNPCDutchman::ParseINI() +{ + zNPCCommon::ParseINI(); + cfg_npc->snd_traxShare = g_sndTrax_Dutchman; + NPCS_SndTablePrepare(g_sndTrax_Dutchman); + cfg_npc->snd_trax = g_sndTrax_Dutchman; + NPCS_SndTablePrepare(g_sndTrax_Dutchman); + tweak.load(parmdata, pdatsize); +} + +namespace +{ + void tweak_group::register_tweaks(bool init, xModelAssetParam* ap, U32 apsize, const char*) + { + if (init) + { + orbit_radius = 13.0f; + auto_tweak::load_param(orbit_radius, 1.0f, 0.01f, 50.0f, ap, apsize, + "orbit_radius"); + } + if (init) + { + accel = xVec3::create(1.0f, 3.0f, 2.5f); + auto_tweak::load_param(accel, 0, 0, 0, ap, apsize, "accel"); + } + if (init) + { + max_vel = xVec3::create(1.0f, 3.0f, 2.5f); + auto_tweak::load_param(max_vel, 0, 0, 0, ap, apsize, "max_vel"); + } + if (init) + { + turn_accel = 540.0f; + auto_tweak::load_param(turn_accel, DEG2RAD(10), 0.01f, 1000000000.0f, ap, + apsize, "turn_accel"); + } + if (init) + { + turn_max_vel = 180.0f; + auto_tweak::load_param(turn_max_vel, DEG2RAD(10), 0.01f, 1000000000.0f, ap, + apsize, "turn_max_vel"); + } + if (init) + { + ground_y = -1.4f; + auto_tweak::load_param(ground_y, 1.0f, -1000000000.0f, 1000000000.0f, ap, + apsize, "ground_y"); + } + if (init) + { + ground_radius = 12.0f; + auto_tweak::load_param(ground_radius, 1.0f, 0.0f, 1000000000.0f, ap, apsize, + "ground_radius"); + } + if (init) + { + alpha = 1.0f; + auto_tweak::load_param(alpha, 1.0f, 0.0f, 1.0f, ap, apsize, "alpha"); + } + if (init) + { + speed_mult[0] = 1.0f; + auto_tweak::load_param(speed_mult[0], 1.0f, 0.01f, 10.0f, ap, apsize, + "speed_mult[0]"); + } + if (init) + { + speed_mult[1] = 1.5f; + auto_tweak::load_param(speed_mult[1], 1.0f, 0.01f, 10.0f, ap, apsize, + "speed_mult[1]"); + } + if (init) + { + speed_mult[2] = 2.0f; auto_tweak::load_param(speed_mult[2], 1.0f, 0.01f, 10.0f, ap, apsize, "speed_mult[2]"); } @@ -628,7 +1073,7 @@ namespace { flame.sneeze_mult = 10.0f; auto_tweak::load_param(flame.sneeze_mult, 1.0f, 1.0f, 100.0f, ap, apsize, - "flame.sneete_mult"); + "flame.sneeze_mult"); } if (init) { @@ -963,937 +1408,2300 @@ namespace apsize, "sound[SOUND_MORE_BIZARRE].delay"); } } - } // namespace -//13 new states -//8 new transitions -xAnimTable* ZNPC_AnimTable_Dutchman() +void zNPCDutchman::SelfSetup() { - // clang-format off - S32 ourAnims[13] = { - ANIM_Idle01, - ANIM_Fidget01, - ANIM_Fidget02, - ANIM_Fidget03, - ANIM_Taunt01, - ANIM_Death01, - ANIM_AttackWindup01, - ANIM_AttackLoop01, - ANIM_AttackEnd01, - ANIM_Attack02Windup01, - ANIM_Attack02Loop01, - ANIM_Attack02End01, - ANIM_LassoGrab01, - - }; - // clang-format on - xAnimTable* table = xAnimTableNew("zNPCDutchman", NULL, 0); + xBehaveMgr* bmgr = xBehaveMgr_GetSelf(); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Idle01], 0x10, 0, f1606, NULL, NULL, f1605, NULL, - NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Death01], 0, 0, f1606, NULL, NULL, f1605, NULL, - NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Fidget01], 0x20, 0, f1606, NULL, NULL, f1605, - NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Fidget02], 0x20, 0, f1606, NULL, NULL, f1605, - NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Fidget03], 0x20, 0, f1606, NULL, NULL, f1605, - NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Taunt01], 0x20, 0, f1606, NULL, NULL, f1605, - NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_AttackWindup01], 0x20, 0, f1606, NULL, NULL, - f1605, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_AttackLoop01], 0x10, 0, f1606, NULL, NULL, f1605, - NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_AttackEnd01], 0x20, 0, f1606, NULL, NULL, f1605, - NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Attack02Windup01], 0x20, 0, f1606, NULL, NULL, - f1605, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Attack02Loop01], 0x10, 0, f1606, NULL, NULL, - f1605, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_Attack02End01], 0x20, 0, f1606, NULL, NULL, - f1605, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); - xAnimTableNewState(table, g_strz_subbanim[ANIM_LassoGrab01], 0x20, 0x2000000, f1606, NULL, NULL, - f1605, NULL, NULL, xAnimDefaultBeforeEnter, NULL, NULL); + psy_instinct = bmgr->Subscribe(this, 0); - NPCC_BuildStandardAnimTran(table, g_strz_subbanim, ourAnims, 1, f1689); + xPsyche* psy = psy_instinct; - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_AttackWindup01], - g_strz_subbanim[ANIM_AttackLoop01], 0, 0, 0x10, 0, 0, 0, 0, 0, f1690, - 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_AttackLoop01], - g_strz_subbanim[ANIM_AttackEnd01], 0, 0, 0, 0, 0, 0, 0, 0, f1690, 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Attack02Windup01], - g_strz_subbanim[ANIM_Attack02Loop01], 0, 0, 0x10, 0, 0, 0, 0, 0, f1690, - 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Attack02Loop01], - g_strz_subbanim[ANIM_Attack02End01], 0, 0, 0, 0, 0, 0, 0, 0, f1690, 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Fidget02], g_strz_subbanim[ANIM_Idle01], 0, - 0, 0x10, 0, 0, 0, 0, 0, f1690, 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Fidget02], - g_strz_subbanim[ANIM_AttackWindup01], 0, 0, 0, 0, 0, 0, 0, 0, f1690, 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_Fidget02], g_strz_subbanim[ANIM_Fidget01], - 0, 0, 0, 0, 0, 0, 0, 0, f1690, 0); - xAnimTableNewTransition(table, g_strz_subbanim[ANIM_LassoGrab01], g_strz_subbanim[ANIM_Death01], - 0, 0, 0, 0, 0, 0, 0, 0, f1690, 0); + psy->BrainBegin(); - return table; + for (S32 i = NPC_GOAL_DUTCHMANNIL; i <= NPC_GOAL_DUTCHMANDEATH; i++) + { + psy->AddGoal(i, this); + } + + psy->BrainEnd(); + psy->SetSafety(NPC_GOAL_DUTCHMANIDLE); } -void zNPCDutchman::Init(xEntAsset* asset) +void zNPCDutchman::Damage(en_NPC_DAMAGE_TYPE, xBase*, const xVec3*) { - // Function is at 60% - // Laser_texture and m both need to be used? + xPsyche* psy = this->psy_instinct; + psy->GIDOfActive(); +} - char* scorch_name[2]; - S32 i; - RwTexture* laser_texture; - S32 model_index; - xModelInstance* m = model; - xFXRibbon* ribbon; +U32 zNPCDutchman::AnimPick(S32 rawgoal, en_NPC_GOAL_SPOT gspot, xGoal* goal) +{ + S32 index = -1; + U32 animID = 0; - dutchman_count = dutchman_count + 1; - boss_cam.init(); - zNPCCommon::Init(asset); - flg_move = 1; - ribbon = eye_scorch; - flg_vuln = 1; + switch (rawgoal) + { + case NPC_GOAL_DUTCHMANNIL: + case NPC_GOAL_DUTCHMANDAMAGE: + case NPC_GOAL_DUTCHMANDEATH: + index = -1; + break; + case NPC_GOAL_DUTCHMANINITIATE: + case NPC_GOAL_DUTCHMANIDLE: + case NPC_GOAL_DUTCHMANDISAPPEAR: + case NPC_GOAL_DUTCHMANTELEPORT: + index = 1; + break; + case NPC_GOAL_DUTCHMANREAPPEAR: + index = 5; + break; + case NPC_GOAL_DUTCHMANBEAM: + index = 0xC; + break; + case NPC_GOAL_DUTCHMANFLAME: + index = 0x10; + break; + case NPC_GOAL_DUTCHMANPOSTFLAME: + if (flag.hurting != false) + { + index = 4; + } + else + { + index = 6; + } + break; + case NPC_GOAL_DUTCHMANCAUGHT: + index = 0x13; + break; + + default: + index = 1; + break; + } - for (i = 0; i < 2; i++) + if (index > -1) { - ribbon->init((S32)eye_scorch, (const char*)0x1ff); - ribbon->set_default_config(); - ribbon->set_curve(ribbon->curve, 0x7); - ribbon->set_texture("fx_streak1"); - ribbon->cfg.life_time = 5.0; - ribbon->refresh_config(); + animID = g_hash_subbanim[index]; } - laser_raster = (RwRaster*)xSTFindAsset((U32)xStrHash("laser_beam_white_blue"), 0x0); + return animID; +} + +void zNPCDutchman::LassoNotify(en_LASSO_EVENT event) +{ + switch (event) + { + case LASS_EVNT_BEGIN: + break; + case LASS_EVNT_GRABSTART: + psy_instinct->GoalSet(NPC_GOAL_DUTCHMANCAUGHT, 1); + break; + case LASS_EVNT_GRABEND: + break; + } - waves.init(0xf); - slime.slices.init(0x3f); + zNPCCommon::LassoNotify(event); } -void zNPCDutchman::Setup() +S32 zNPCDutchman::LassoSetup() { - zNPCSubBoss::Setup(); + zNPCCommon::LassoUseGuides(1, 1); + return zNPCCommon::LassoSetup(); } -void zNPCDutchman::Reset() +void zNPCDutchman::update_round() { - // Best I can get it - // xFXRibbon::clear doenst make much sense - zNPCCommon::Reset(); - memset((void*)flag.face_player, 0, 16); - decompose(); - life = 3; - round = 0; - stage = -1; - alpha = 1.0f; - update_round(); - face_player(); - flg_vuln = 1; - reset_speed(); - move.vel = 0.0f; - move.dest = get_center(); - flag.move = MOVE_FOLLOW; - flames.imax_dist = 1.0f / tweak.ground_radius; - reset_blob_mat(); - waves.clear(); - slime.slices.clear(); - //eye_scorch->joints.clear(); - //eye_scorch->joints.clear(); - fade.sound_handle = 0; - vanish(); - refresh_reticle(); - flag.fighting = 0; - plasma_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_PLASMA"); - plasma_emitter_settings.custom_flags = 0x100; - plasma_emitter_settings.pos = g_O3; - spark_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_SPARKS"); - spark_emitter_settings.custom_flags = 0x100; - spark_emitter_settings.pos = g_O3; - light_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_LIGHT"); - light_emitter_settings.custom_flags = 0x110; - light_emitter_settings.pos = g_O3; - light_emitter->prop->life.set((119.99999f * tweak.beam.light_rate)); - eyeglow_emitter[0] = zParEmitterFind("PAREMIT_DUTCHMAN_EYEGLOW0"); - eyeglow_emitter[1] = zParEmitterFind("PAREMIT_DUTCHMAN_EYEGLOW1"); - death_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_DEATH"); - dissolve_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_DISSOLVE"); - fadeout_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_FADEOUT"); - fadein_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_FADEIN"); - flame_emitter[0] = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_LIGHT"); - flame_emitter[1] = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_NORMAL"); - flame_emitter[2] = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_SPRAY"); - flame_emitter_settings.custom_flags = 0x110; - flame_emitter_settings.pos = g_O3; - light_emitter->prop->life.set((59.999996f)); - snot_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_FLAME_SNOT"); - snot_emitter_settings.custom_flags = 0x300; - slime_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_SLIME_TRAIL"); - slime_emitter_settings.custom_flags = 0x100; - hand_trail_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_HAND_TRAIL"); - blob_emitter = zParEmitterFind("PAREMIT_DUTCHMAN_BLOB"); - psy_instinct->GoalSet('NGM=', 1); + S32 roundCntr = round; + if (life == 0) + { + round = 3; + } + else + { + round = 2 - ((life + -1) * 3) / 3; + } + if (round == roundCntr) + { + return; + } + + stage = -1; } -void zNPCDutchman::Destroy() +S32 zNPCDutchman::next_goal() { - zNPCCommon::Destroy(); - dutchman_count--; + stage++; + + if (sequence[round][stage].goal == 0) + { + stage = 0; + } + + delay = 0.0f; + + return sequence[round][stage].goal; } -void zNPCDutchman::Process(xScene* xscn, F32 dt) +F32 zNPCDutchman::goal_delay() { - xVec3 player_loc; + return sequence[round][stage].delay; +} + +void zNPCDutchman::decompose() +{ + if (flag.fighting) + { + flag.fighting = false; + disable_emitter(*dissolve_emitter); + disable_emitter(*fadein_emitter); + disable_emitter(*fadeout_emitter); + zCameraEnableTracking(CO_BOSS); + boss_cam.stop(); + } +} - if (flag.fighting == 0) +namespace +{ + void set_yaw_matrix(xMat3x3& mat, F32 dt) { - zNPCCommon::Process(xscn, dt); + F32 tempSin; + F32 tempCos; + + tempSin = isin(dt); + tempCos = icos(dt); + mat.right.assign(tempCos, 0.0f, -tempSin); + mat.up.assign(0.0f, 1.0f, 0.0f); + mat.at.assign(tempSin, 0.0f, tempCos); + } + + void update_move_follow(xVec3& loc, zNPCDutchman::move_info& move, const xMat3x3& mat, F32 dt) + { + xVec3 offset = move.dest - loc; + + xMat3x3LMulVec(&offset, &mat, &offset); + + xVec3 dloc = { 0.0f, 0.0f, 0.0f }; + + xAccelMove(dloc.x, move.vel.x, move.accel.x, dt, offset.x, move.max_vel.x); + xAccelMove(dloc.y, move.vel.y, move.accel.y, dt, offset.y, move.max_vel.y); + xAccelMove(dloc.z, move.vel.z, move.accel.z, dt, offset.z, move.max_vel.z); + + xMat3x3RMulVec(&dloc, &mat, &dloc); + + loc += dloc; + } + + void update_move_accel(xVec3& loc, zNPCDutchman::move_info& move, F32 dt) + { + loc += (move.accel * ((0.5f * dt) * dt)) + (move.vel * dt); + move.vel += move.accel * dt; + } + + void update_move_vel(xVec3& loc, zNPCDutchman::move_info& move, F32 dt) + { + loc += move.vel * dt; + } + + void update_move_stop(xVec3& loc, zNPCDutchman::move_info& move, F32 dt) + { + xAccelStop(loc.x, move.vel.x, move.accel.x, dt); + xAccelStop(loc.y, move.vel.y, move.accel.y, dt); + xAccelStop(loc.z, move.vel.z, move.accel.z, dt); + } +} // namespace + +void zNPCDutchman::update_turn(F32 dt) +{ + const xVec3& loc3 = get_center(); + xVec2 start_dir = { 0.0f, 0.0f }; + + start_dir.x = model->Mat->at.x; + start_dir.y = model->Mat->at.z; + + if (turning()) + { + F32 start = xatan2(start_dir.x, start_dir.y); + F32 end = xatan2(turn.dir.x, turn.dir.y); + F32 diff = end - start; + + if (diff > PI) + { + diff -= 2.0f * PI; + } + else if (diff < -PI) + { + diff += 2.0f * PI; + } + + F32 yaw = start; + + xAccelMove(yaw, turn.vel, turn.accel, dt, yaw + diff, turn.max_vel); + set_yaw_matrix(frame->mat, yaw); + } +} + +void zNPCDutchman::update_move(F32 dt) +{ + switch (flag.move) + { + case MOVE_FOLLOW: + update_move_follow(frame->mat.pos, move, frame->mat, dt); + break; + case MOVE_ACCEL: + update_move_accel(frame->mat.pos, move, dt); + break; + case MOVE_VEL: + update_move_vel(frame->mat.pos, move, dt); + break; + case MOVE_STOP: + update_move_stop(frame->mat.pos, move, dt); + break; + } +} + +void zNPCDutchman::render_debug() +{ +} + +void zNPCDutchman::update_animation(F32) +{ +} + +void zNPCDutchman::update_camera(F32 dt) +{ + zCameraDisableTracking(CO_BOSS); + if ((zCameraIsTrackingDisabled() & ~8) == 0) + { + boss_cam.update(dt); + } +} + +namespace +{ + U8 clip_outside_circle(F32& out, const xVec2& origin, const xVec2& dir, F32 d1, F32 d2, + const xVec2& center, F32 r) + { + F32 r2 = r * r; + F32 len = d2 - d1; + xVec2 offset = dir * len; + xVec2 g = origin + (dir * d1); + xVec2 h = g + offset; + xVec2 dh = h - center; + F32 a = dh.length2(); + + if (a <= r2) + { + return FALSE; + } + + xVec2 dg = g - center; + + if (dg.length2() > r2) + { + out = d1; + return TRUE; + } + + F32 len2 = len * len; + F32 b = 2.0f * offset.dot(dh); + F32 d = b * b - (4.0f * len2) * (a - r2); + + if (d < 0.0f) + { + return FALSE; + } + + out = (0.5f / len2) * (-b + xsqrt(d)); + + return TRUE; + } + + U8 clip_outside_circle(F32& out, const xVec3& origin, const xVec3& dir, F32 d1, F32 d2, + const xVec3& center, F32 r) + { + xVec2 origin2 = { 0.0f, 0.0f }; + xVec2 dir2 = { 0.0f, 0.0f }; + xVec2 center2 = { 0.0f, 0.0f }; + + origin2.x = origin.x; + origin2.y = origin.z; + dir2.x = dir.x; + dir2.y = dir.z; + center2.x = center.x; + center2.y = center.z; + + return clip_outside_circle(out, origin2, dir2, d1, d2, center2, r); + } +} // namespace + +void zNPCDutchman::update_wave(zNPCDutchman::wave_data& wave, F32 dt) +{ + F32 old_dist = wave.dist; + + xAccelMove(wave.dist, wave.vel, tweak.flame.accel, dt, tweak.flame.max_vel); + + if (wave.clipped) + { + return; + } + + F32 dist; + + if (clip_outside_circle(wave.clip_dist, wave.loc, wave.dir, old_dist, wave.dist, get_orbit(), + tweak.ground_radius)) + { + wave.clipped = TRUE; + dist = wave.clip_dist; + + if (wave.sound_handle != 0) + { + kill_sound(1, wave.sound_handle); + wave.sound_handle = 0; + } } else { - delay = delay + dt; - psy_instinct->Timestep(dt, NULL); - if (flag.fighting == 0) + dist = wave.dist; + } + + F32 frac = 1.0f - (dist * flames.imax_dist) * tweak.flame.decay; + F32 diff = dist - old_dist; + xVec3 tan = { 0.0f, 0.0f, 0.0f }; + + tan.x = wave.dir.z; + tan.z = -wave.dir.x; + + for (S32 i = 0; i < 3; i++) + { + xParEmitterPropsAsset& prop = *flame_emitter[i]->prop; + F32 old_life[2] = { 0.0f, 0.0f }; + F32 old_size_birth[2] = { 0.0f, 0.0f }; + F32 old_size_death[2] = { 0.0f, 0.0f }; + + old_life[0] = prop.life.val[0]; + old_life[1] = prop.life.val[1]; + old_size_birth[0] = prop.size_birth.val[0]; + old_size_birth[1] = prop.size_birth.val[1]; + old_size_death[0] = prop.size_death.val[0]; + old_size_death[1] = prop.size_death.val[1]; + + prop.life.val[0] *= frac; + prop.life.val[1] *= frac; + prop.size_birth.val[0] *= frac; + prop.size_birth.val[1] *= frac; + prop.size_death.val[0] *= frac; + prop.size_death.val[1] *= frac; + + S32 emit = (S32)(dist * tweak.flame.emit_rate[i]) + 1; + + while (wave.emitted[i] < emit) { - zNPCCommon::Process(xscn, dt); + F32 dist0 = diff * xurand() + old_dist; + F32 dist1 = tweak.flame.emit_width[i] * (xurand() - 0.5f); + + flame_emitter_settings.pos = (wave.loc + wave.dir * dist0) + tan * dist1; + + emit_particles(*flame_emitter[i], FRAME_TIME, flame_emitter_settings); + + wave.emitted[i]++; } - else + + prop.life.val[0] = old_life[0]; + prop.life.val[1] = old_life[1]; + prop.size_birth.val[0] = old_size_birth[0]; + prop.size_birth.val[1] = old_size_birth[1]; + prop.size_death.val[0] = old_size_death[0]; + prop.size_death.val[1] = old_size_death[1]; + } +} + +void zNPCDutchman::init_wave(zNPCDutchman::wave_data& wave, const xVec3& loc, const xVec3& dir) +{ + wave.clipped = FALSE; + wave.loc = loc + (dir * tweak.flame.start_dist); + wave.dir = dir; + wave.dist = 0.0f; + wave.vel = 0.0f; + wave.sound_loc = wave.loc; + wave.sound_handle = play_sound(1, &wave.sound_loc, 1.0f); + wave.emitted[0] = 0; + wave.emitted[1] = 0; + wave.emitted[2] = 0; +} + +void zNPCDutchman::kill_wave(zNPCDutchman::wave_data& wave) +{ + kill_sound(1, wave.sound_handle); +} + +void zNPCDutchman::start_eye_glow() +{ + flag.eye_glow = true; + eye_glow.size = 1; +} + +void zNPCDutchman::stop_eye_glow() +{ + flag.eye_glow = false; +} + +void zNPCDutchman::update_eye_glow(F32 dt) +{ + if (flag.eye_glow) + { + xVec3 offset = get_facing() * tweak.beam.glow_dist; + + for (S32 i = 0; i < 2; i++) { - if (flag.face_player != 0) - { - player_loc = globals.player.ent.model->Scale; - get_center(); - } - update_turn(dt); - update_move(dt); - update_animation(dt); - update_flames(dt); - update_eye_glow(dt); - update_hand_trail(dt); - update_fade(dt); - update_slime(dt); + xParEmitterAsset& ea = *eyeglow_emitter[i]->tasset; + xParEmitterPropsAsset& prop = *eyeglow_emitter[i]->prop; - if ((check_player_damage() & 0xff) != 0) - { - zEntPlayer_Damage((xBase*)this, 1); - } - update_camera(dt); - refresh_reticle(); - flg_xtrarend = flg_xtrarend | 1; - zNPCCommon::Process(xscn, dt); + ea.pos = get_eye_loc(i) + offset; + + F32 old_size_birth[2] = { 0.0f, 0.0f }; + F32 old_size_death[2] = { 0.0f, 0.0f }; + + old_size_birth[0] = prop.size_birth.val[0]; + old_size_birth[1] = prop.size_birth.val[1]; + old_size_death[0] = prop.size_death.val[0]; + old_size_death[1] = prop.size_death.val[1]; + + prop.size_birth.val[0] *= eye_glow.size; + prop.size_birth.val[1] *= eye_glow.size; + prop.size_death.val[0] *= eye_glow.size; + prop.size_death.val[1] *= eye_glow.size; + + emit_particles(*eyeglow_emitter[i], dt); + + prop.size_birth.val[0] = old_size_birth[0]; + prop.size_birth.val[1] = old_size_birth[1]; + prop.size_death.val[0] = old_size_death[0]; + prop.size_death.val[1] = old_size_death[1]; } } } -S32 zNPCDutchman::SysEvent(xBase* from, xBase* to, U32 toEvent, const F32* toParam, - xBase* toParamWidget, S32* handled) +void zNPCDutchman::start_hand_trail() +{ + flag.hand_trail = true; + + for (S32 i = 0; i < 2; i++) + { + hand_trail.loc[i] = get_hand_loc(i); + } +} + +void zNPCDutchman::stop_hand_trail() +{ + flag.hand_trail = false; +} + +void zNPCDutchman::update_hand_trail(F32 dt) +{ + if (flag.hand_trail) + { + xParEmitterAsset& ea = *hand_trail_emitter->tasset; + + ea.emit_type = eParEmitterLine; + ea.e_line.radius = tweak.teleport.trail_width; + + for (S32 i = 0; i < 2; i++) + { + ea.e_line.pos1 = hand_trail.loc[i]; + hand_trail.loc[i] = get_hand_loc(i); + ea.e_line.pos2 = hand_trail.loc[i]; + + emit_particles(*hand_trail_emitter, dt); + } + } +} + +void zNPCDutchman::dissolve(F32 delay) +{ + F32 volume; + if (delay <= 0.0f) + { + flag.fade = FADE_TELEPORT; + disable_emitter(*fadeout_emitter); + set_alpha(0.0f); + vanish(); + volume = 1.0f; + } + else + { + flag.fade = FADE_DISSOLVE; + fade.time = 0.0f; + fade.duration = delay; + fade.iduration = 1.0f / fade.duration; + enable_emitter(*fadeout_emitter); + set_alpha(1.0f); + reappear(); + volume = 0.0f; + } + enable_emitter(*dissolve_emitter); + start_eye_glow(); + start_hand_trail(); + + if (fade.sound_handle == 0) + { + fade.sound_handle = play_sound(2, &bound.sph.center, volume); + } + else + { + set_volume(2, fade.sound_handle, volume); + } +} + +void zNPCDutchman::coalesce(F32 delay) +{ + reappear(); + if (delay <= 0.0f) + { + flag.fade = FADE_NONE; + disable_emitter(*fadein_emitter); + disable_emitter(*dissolve_emitter); + set_alpha(1.0f); + stop_eye_glow(); + stop_hand_trail(); + if (fade.sound_handle != 0) + { + kill_sound(2, fade.sound_handle); + fade.sound_handle = 0; + } + } + else + { + flag.fade = FADE_COALESCE; + fade.time = 0.0f; + fade.duration = delay; + fade.iduration = 1.0f / fade.duration; + enable_emitter(*fadein_emitter); + enable_emitter(*dissolve_emitter); + set_alpha(0.0f); + start_eye_glow(); + start_hand_trail(); + if (fade.sound_handle != 0) + { + set_volume(2, fade.sound_handle, 1.0f); + } + } +} + +void zNPCDutchman::reset_blob_mat() +{ + xMat3x3& mat = flames.blob_mat; + F32 s = isin(tweak.flame.blob_pitch); + F32 c = icos(tweak.flame.blob_pitch); + + mat.right.assign(1.0f, 0.0f, 0.0f); + mat.up.assign(0.0f, c, s); + mat.at.assign(0.0f, -s, c); +} + +void zNPCDutchman::refresh_reticle() +{ + dutchman_reticle_center = xModelGetBoneLocation(*model, 47); + dutchman_reticle_center.y += tweak.reticle_y; +} + +void zNPCDutchman::reset_lasso_anim() +{ + xAnimPlaySetState(lassdata->grabGuideModel->Anim->Single, lassdata->grabGuideAnim, 0.0f); +} + +void zNPCDutchman::update_fade(F32 dt) +{ + switch (flag.fade) + { + case FADE_DISSOLVE: + { + fade.time = fade.time + dt; + + if (fade.time >= fade.duration) + { + flag.fade = FADE_TELEPORT; + disable_emitter(*fadeout_emitter); + set_alpha(0.0f); + vanish(); + set_volume(2, fade.sound_handle, 1.0f); + } + else + { + F32 frac = fade.time * fade.iduration; + + set_alpha(1.0f - frac); + set_volume(2, fade.sound_handle, frac); + } + + break; + } + case FADE_TELEPORT: + break; + case FADE_COALESCE: + { + fade.time = fade.time + dt; + + if (fade.time >= fade.duration) + { + flag.fade = FADE_NONE; + disable_emitter(*fadein_emitter); + disable_emitter(*dissolve_emitter); + set_alpha(1.0f); + stop_eye_glow(); + stop_hand_trail(); + reappear(); + kill_sound(2, fade.sound_handle); + fade.sound_handle = 0; + } + else + { + F32 frac = fade.time * fade.iduration; + + set_alpha(frac); + set_volume(2, fade.sound_handle, 1.0f - frac); + } + + break; + } + } +} + +void zNPCDutchman::update_slime(F32 dt) +{ + static_queue::iterator it = slime.slices.begin(); + + while (it != slime.slices.end()) + { + slime_slice& slice = *it; + + slice.age += dt; + + if (slice.age > tweak.damage.slime_time) + { + slime.slices.erase(it, slime.slices.end()); + break; + } + + ++it; + } +} + +void zNPCDutchman::add_slime(const xVec3& loc, F32 dt) +{ + slime_emitter_settings.pos = loc; + + emit_particles(*slime_emitter, dt, slime_emitter_settings); + + if (slime.slices.empty()) + { + slime.origin.assign(loc.x, tweak.ground_y, loc.z); + slime.dir.assign(move.dest.x - loc.x, 0.0f, move.dest.z - loc.z); + slime.dir.normalize(); + + slime.slices.push_front(); + + slime_slice& slice = slime.slices.front(); + + slice.age = 0.0f; + slice.dist = 0.0f; + } + else + { + slime_slice& front = slime.slices.front(); + F32 dist2 = (loc - slime.origin).length2(); + F32 max_dist = 0.5f + front.dist; + + if (dist2 >= max_dist * max_dist) + { + if (slime.slices.full()) + { + slime.slices.pop_back(); + } + + slime.slices.push_front(); + + slime_slice& slice = slime.slices.front(); + + slice.age = 0.0f; + slice.dist = xsqrt(dist2); + } + } +} + +void zNPCDutchman::add_spray(const xVec3& loc, F32 dt) +{ + const xVec3& facing = get_facing(); + xVec3 emit_loc = loc + (facing * tweak.flame.snot_dist); + + emit_loc.y += tweak.flame.snot_height; + + snot_emitter_settings.pos = emit_loc; + snot_emitter_settings.vel = facing * tweak.flame.snot_vel; + + if (flames.time < tweak.flame.warm_up_time) + { + F32 frac = tweak.flame.sneeze_mult; + xParEmitterPropsAsset& prop = *snot_emitter->prop; + F32 old_rate[2] = { 0.0f, 0.0f }; + + old_rate[0] = prop.rate.val[0]; + old_rate[1] = prop.rate.val[1]; + + prop.rate.val[0] *= frac; + prop.rate.val[1] *= frac; + + emit_particles(*snot_emitter, dt, snot_emitter_settings); + + prop.rate.val[0] = old_rate[0]; + prop.rate.val[1] = old_rate[1]; + } + else + { + emit_particles(*snot_emitter, dt, snot_emitter_settings); + } + + if (flames.time >= tweak.flame.warm_up_time) + { + xParEmitterAsset& ea = *blob_emitter->tasset; + F32 velmag = ea.vel.y; + + ea.emit_type = eParEmitterLine; + ea.e_line.radius = tweak.flame.spray_width; + + if (flames.blob_break) + { + ea.vel = 0.0f; + ea.e_line.pos1 = ea.e_line.pos2 = flames.blob_loc = emit_loc; + flames.blob_break = FALSE; + } + else + { + ea.vel = (emit_loc - flames.blob_loc) / dt; + ea.e_line.pos1 = flames.blob_loc; + flames.blob_loc = emit_loc; + ea.e_line.pos2 = flames.blob_loc; + + F32 len2 = ea.vel.length2(); + + if (len2 > velmag * velmag) + { + ea.vel *= velmag / xsqrt(len2); + } + } + + xVec3 extra_vel = facing * velmag; + + xMat3x3Rot(&flames.blob_mat, (xVec3*)&model->Mat->right, tweak.flame.blob_pitch); + xMat3x3RMulVec(&extra_vel, &flames.blob_mat, &extra_vel); + + ea.vel += extra_vel; + + emit_particles(*blob_emitter, dt); + + ea.vel.y = velmag; + } +} + +void zNPCDutchman::add_splash(const xVec3&, F32) +{ +} + +void zNPCDutchman::update_flames(F32 dt) +{ + static_queue::iterator it = waves.begin(); + + while (it != waves.end()) + { + wave_data& wave = *it; + + update_wave(wave, dt); + + if (wave.dist >= tweak.ground_radius) + { + static_queue::iterator itp = it; + + while (itp != waves.end()) + { + kill_wave(*itp); + ++itp; + } + + waves.erase(it, waves.end()); + break; + } + + ++it; + } + + if (flag.flaming) + { + flames.time += dt; + + const xVec3& facing = get_facing(); + xVec3 nose_loc = get_nose_loc(); + + add_spray(nose_loc, dt); + + xVec3 ground_loc = { 0.0f, 0.0f, 0.0f }; + + ground_loc.x = nose_loc.x + facing.x * tweak.flame.lead_dist; + ground_loc.z = nose_loc.z + facing.z * tweak.flame.lead_dist; + ground_loc.y = tweak.ground_y; + + const xVec3& orbit = get_orbit(); + xVec2 orbit_offset = { 0.0f, 0.0f }; + + orbit_offset.x = ground_loc.x - orbit.x; + orbit_offset.y = ground_loc.z - orbit.z; + + if (orbit_offset.length2() > tweak.ground_radius * tweak.ground_radius) + { + return; + } + + add_slime(ground_loc, dt); + add_splash(ground_loc, dt); + + S32 emit = (S32)(flames.time * tweak.flame.wave_rate) + 1; + + if (emit > flames.emitted) + { + flames.emitted = emit; + + xVec3 tan = { 0.0f, 0.0f, 0.0f }; + + tan.z = -facing.x; + tan.x = facing.z; + + if (waves.full()) + { + kill_wave(waves.back()); + waves.pop_back(); + } + + waves.push_front(); + init_wave(waves.front(), ground_loc, tan); + + if (waves.full()) + { + kill_wave(waves.back()); + waves.pop_back(); + } + + waves.push_front(); + init_wave(waves.front(), ground_loc, -tan); + } + } +} + +void zNPCDutchman::start_fight() +{ + if (!flag.fighting) + { + if (life > 0) + { + flag.fighting = true; + + psy_instinct->GoalSet(NPC_GOAL_DUTCHMANINITIATE, 1); + zCameraDisableTracking(CO_BOSS); + boss_cam.start(globals.camera); + boss_cam.set_targets((xVec3&)globals.player.ent.model->Mat->pos, bound.sph.center, + bound.sph.r); + } + } +} + +void zNPCDutchman::set_life(S32 lf) +{ + S32 old_life = life; + + life = range_limit(lf, 0, 3); + + if (life < old_life) + { + flag.hurting = true; + + for (S32 i = life; i < old_life; i++) + { + zEntEvent(this, this, eEventNPCHPDecremented); + } + } +} + +void zNPCDutchman::start_beam() +{ + if (flag.beaming) + { + return; + } + + flag.beaming = true; + flag.was_beaming = false; + beam[1].segments = 0; + beam[0].segments = 0; +} + +void zNPCDutchman::stop_beam() +{ + flag.beaming = false; +} + +void zNPCDutchman::set_alpha(F32 a) +{ + alpha = a; + + F32 model_alpha = a * tweak.alpha; + + for (xModelInstance* m = model; m != NULL; m = m->Next) + { + if (model_alpha < 1.0f) + { + m->Flags |= 0x4000; + } + else + { + m->Flags &= 0xbfff; + } + + m->Alpha = model_alpha; + } +} + +void zNPCDutchman::start_flames() +{ + flag.flaming = true; + flames.time = 0.0f; + flames.emitted = 0; + flames.blob_break = TRUE; + flames.splash_break = TRUE; + slime.slices.clear(); +} + +void zNPCDutchman::stop_flames() +{ + flag.flaming = false; +} + +U8 zNPCDutchman::check_player_damage() +{ + if (globals.player.cheat_mode != 0) + { + return FALSE; + } + + const xSphere& o = globals.player.ent.bound.sph; + + if (flag.beaming) + { + for (S32 which = 0; which < 2; which++) + { + beam_info& b = beam[which]; + + for (U32 i = 0; i < b.segments; i++) + { + if (xSphereHitsCapsule(o, b.start_loc, b.end[i].loc, tweak.damage.beam_radius)) + { + return TRUE; + } + + if (xSphereHitsSphere(o.center, o.r, b.end[i].loc, tweak.damage.beam_blast_radius)) + { + return TRUE; + } + } + } + } + + for (static_queue::iterator it = waves.begin(); it != waves.end(); ++it) + { + wave_data& wave = *it; + xBox box; + xMat4x3 mat; + + F32 frac = + tweak.damage.flame_size.y * (1.0f - (wave.dist * flames.imax_dist) * tweak.flame.decay); + + box.upper.x = 0.5f * tweak.damage.flame_size.x; + box.upper.y = frac; + box.lower.x = -(0.5f * tweak.damage.flame_size.x); + box.lower.y = 0.0f; + box.upper.z = 0.0f; + box.lower.z = -tweak.damage.flame_size.z; + + look_at(mat, wave.dir); + mat.pos = wave.loc + (wave.dir * wave.dist); + + if (xSphereHitsOBB(o, box, mat)) + { + return TRUE; + } + } + + if (flag.hand_trail) + { + for (S32 i = 0; i < 2; i++) + { + if (xSphereHitsSphere(o.center, o.r, hand_trail.loc[i], tweak.damage.hand_radius)) + { + return TRUE; + } + } + } + + if (flag.flaming) + { + xBox box; + xMat4x3 mat; + + box.upper.x = 0.5f * tweak.damage.snot_size.x; + box.upper.y = tweak.damage.snot_size.y; + box.lower.x = -(0.5f * tweak.damage.snot_size.x); + box.lower.y = 0.0f; + box.upper.z = tweak.damage.snot_size.z; + box.lower.z = 0.0f; + + look_at(mat, get_facing()); + mat.pos = get_nose_loc(); + mat.pos.y = tweak.ground_y; + + if (xSphereHitsOBB(o, box, mat)) + { + return TRUE; + } + } + + if (slime.slices.size() > 1) + { + xBox box; + xMat4x3 mat; + + box.upper.x = 0.5f * tweak.damage.slime_width; + box.lower.x = -(0.5f * tweak.damage.slime_width); + box.upper.y = 0.01f; + box.lower.y = 0.0f; + box.upper.z = slime.slices.front().dist; + box.lower.z = slime.slices.back().dist; + + look_at(mat, slime.dir); + mat.pos = slime.origin; + + if (xSphereHitsOBB(o, box, mat)) + { + return TRUE; + } + } + + return FALSE; +} + +xVec3 zNPCDutchman::get_eye_loc(S32 index) const +{ + static const U32 lookup[] = { 10, 13 }; + + return xModelGetBoneLocation(*model, lookup[index]); +} + +xVec3 zNPCDutchman::get_hand_loc(S32 index) const +{ + static const U32 lookup[] = { 32, 40 }; + + return xModelGetBoneLocation(*model, lookup[index]); +} + +xVec3 zNPCDutchman::get_splash_loc() const +{ + const xVec3& facing = get_facing(); + xVec3 nose = get_nose_loc(); + xVec3 loc = { 0.0f, 0.0f, 0.0f }; + + loc.x = nose.x + facing.x * tweak.flame.lead_dist; + loc.y = tweak.ground_y; + loc.z = nose.z + facing.z * tweak.flame.lead_dist; + + return loc; +} + +void zNPCDutchman::vanish() +{ + old.moreFlags = moreFlags; + pflags = 0; + moreFlags = 0; + flags2.flg_colCheck = 0; + flags2.flg_penCheck = 0; + chkby = 0; + penby = 0; + xEntHide(this); +} + +void zNPCDutchman::reappear() +{ + moreFlags = old.moreFlags; + xNPCBasic::RestoreColFlags(); + xEntShow(this); +} + +xVec3 zNPCDutchman::random_orbit(const xVec3& loc, F32 min_ang, F32 max_ang) const +{ + const xVec3& orbit = get_orbit(); + xVec3 offset = loc - orbit; + F32 ang; + F32 len2 = offset.length2(); + + if (len2 >= -0.00001f && len2 <= 0.00001f) + { + ang = 0.0f; + } + else + { + F32 imag = 1.0f / xsqrt(len2); + + ang = xatan2(offset.x * imag, offset.z * imag); + } + + if (max_ang > PI) + { + max_ang = PI; + } + + F32 rand_ang = ((max_ang - min_ang) * xurand()) + min_ang; + + if ((xrand() >> 13) & 1) + { + rand_ang *= -1.0f; + } + + F32 total = ang + rand_ang; + + xVec3 dest = orbit; + + dest.x += tweak.orbit_radius * isin(total); + dest.z += tweak.orbit_radius * icos(total); + + return dest; +} + +void zNPCDutchman::turn_to_face(const xVec3& loc) +{ + flag.face_player = false; + + const xVec3& center = get_center(); + xVec2 dir = { 0.0f, 0.0f }; + + dir.x = loc.x - center.x; + dir.y = loc.z - center.z; + + F32 len2 = dir.length2(); + + if (!(len2 >= -0.00001f && len2 <= 0.00001f)) + { + dir *= 1.0f / xsqrt(len2); + turn.dir = dir; + } +} + +void zNPCDutchman::reset_speed() +{ + turn.accel = tweak.turn_accel; + turn.max_vel = tweak.turn_max_vel; + move.accel = tweak.accel * tweak.speed_mult[round]; + move.max_vel = tweak.max_vel * tweak.speed_mult[round]; +} + +void zNPCDutchman::halt(F32 decel) +{ + flag.move = MOVE_STOP; + + move.accel.x = (move.vel.x < 0.0f) ? decel : -decel; + move.accel.y = (move.vel.y < 0.0f) ? decel : -decel; + move.accel.z = (move.vel.z < 0.0f) ? decel : -decel; +} + +namespace auto_tweak +{ + template <> + void load_param(F32& value, F32 scale, F32 min, F32 max, xModelAssetParam* ap, + U32 apsize, const char* name) + { + value = zParamGetFloat(ap, apsize, name, value); + + if (value < min) + { + value = min; + } + else if (value > max) + { + value = max; + } + + value *= scale; + } + + template <> + void load_param(S32& value, S32 scale, S32 min, S32 max, xModelAssetParam* ap, + U32 apsize, const char* name) + { + S32 result = zParamGetInt(ap, apsize, name, value); + + if (result < min) + { + result = min; + } + else if (result > max) + { + result = max; + } + + value = result * scale; + } + + template <> + void load_param(xVec3& value, S32, S32, S32, xModelAssetParam* ap, U32 apsize, + const char* name) + { + zParamGetVector(ap, apsize, name, value, &value); + } +} // namespace auto_tweak + +namespace +{ + void set_vert(RxObjSpace3DVertex&, const xVec3&, F32, F32, U8); + + void set_beam_verts(RxObjSpace3DVertex* vert, const xVec3& loc0, const xVec3& loc1, U8 a0, + U8 a1, const xVec3& half_right) + { + set_vert(vert[0], loc0 - half_right, 0.0f, 0.0f, a0); + set_vert(vert[1], loc1 - half_right, 1.0f, 0.0f, a1); + set_vert(vert[2], loc0 + half_right, 0.0f, 1.0f, a0); + + vert[3] = vert[2]; + vert[4] = vert[1]; + + set_vert(vert[5], loc1 + half_right, 1.0f, 1.0f, a1); + } + + void set_vert(RxObjSpace3DVertex& vert, const xVec3& loc, F32 u, F32 v, U8 alpha) + { + vert.x = loc.x; + vert.y = loc.y; + vert.z = loc.z; + vert.u = u; + vert.v = v; + vert.r = 255; + vert.g = 255; + vert.b = 255; + vert.a = alpha; + } + + xVec3 world_to_screen(const xVec3& loc) + { + iCameraUpdatePos(globals.camera.lo_cam, &globals.camera.mat); + + xMat4x3& view_mat = (xMat4x3&)globals.camera.lo_cam->viewMatrix; + xVec3 cam_loc; + + xMat4x3Toworld(&cam_loc, &view_mat, &loc); + + xVec3 screen_loc; + F32 iz = 1.0f / cam_loc.z; + + screen_loc.assign(cam_loc.x * iz, cam_loc.y * iz, 1.0f); + + return screen_loc; + } + + RxObjSpace3DVertex* render_beam(RxObjSpace3DVertex* vert, const zNPCDutchman::beam_info& beam, + u32 which, U8 alpha) + { + const xVec3& start_loc = beam.start_loc; + const xVec3& end_loc = beam.end[which].loc; + xVec3 dir = (end_loc - start_loc).normal(); + xVec3 start_screen_loc = world_to_screen(start_loc); + xVec3 end_screen_loc = world_to_screen(end_loc); + xVec3 screen_dir = end_screen_loc - start_screen_loc; + + screen_dir.z = 0.0f; + screen_dir.up_normalize(); + + xVec3 screen_right = { 0.0f, 0.0f, 0.0f }; + + screen_right.x = screen_dir.y; + screen_right.y = -screen_dir.x; + + xVec3 right; + + xMat3x3LMulVec(&right, (xMat3x3*)&globals.camera.lo_cam->viewMatrix, &screen_right); + right.right_normalize(); + + xVec3 half_right = right * (0.5f * beam.thickness); + + set_beam_verts(vert, start_loc, end_loc, alpha, alpha, half_right); + + if (!beam.end[which].extend) + { + return vert + 6; + } + + set_beam_verts(vert + 6, end_loc, start_loc + (dir * tweak.beam.fade_dist), alpha, 0, + half_right); + + return vert + 12; + } + + RxObjSpace3DVertex* render_beam(RxObjSpace3DVertex* vert, const zNPCDutchman::beam_info& beam) + { + U8 alpha = (S32)(0.5f + (255.0f * beam.alpha) / (F32)beam.segments); + u32 segments = beam.segments; + + for (u32 i = 0; i < segments; i++) + { + vert = render_beam(vert, beam, i, alpha); + } + + return vert; + } +} // namespace + +void zNPCDutchman::render_beam() +{ + U32 total = beam[0].segments + beam[1].segments; + RxObjSpace3DVertex* verts = + (RxObjSpace3DVertex*)xMemPushTemp(total * (12 * sizeof(RxObjSpace3DVertex))); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, laser_raster); + + RxObjSpace3DVertex* last = ::render_beam(verts, beam[0]); + + last = ::render_beam(last, beam[1]); + + RwIm3DTransform(verts, last - verts, NULL, + rwIM3D_VERTEXUV | rwIM3D_VERTEXXYZ | rwIM3D_VERTEXRGBA); + RwIm3DRenderPrimitive(rwPRIMTYPETRILIST); + RwIm3DEnd(); + + xMemPopTemp(verts); +} + +void zNPCDutchman::render_halo() +{ + const xMat4x3& mat = globals.camera.mat; + xVec3 right; + xVec3 up; + xVec3 at; + xVec3 center; + + at = mat.at * -tweak.halo.scale; + up = mat.up * tweak.halo.scale; + right = mat.right * -tweak.halo.scale; + center = get_chest_loc(); + + center.y += tweak.halo.yoffset; +} + +xFactoryInst* zNPCGoalDutchmanNil::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanNil(who, (zNPCDutchman&)*info); +} + +xFactoryInst* zNPCGoalDutchmanInitiate::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanInitiate(who, (zNPCDutchman&)*info); +} + +S32 zNPCGoalDutchmanInitiate::Enter(F32 dt, void* updCtxt) +{ + const xVec3& end_loc = owner.get_orbit(); + const xVec3& loc = *owner.nav_curr->PosGet(); + + xVec3& floc = (xVec3&)owner.model->Mat->pos; + + floc = owner.frame->mat.pos = loc; + + xVec2 offset = { 0.0f, 0.0f }; + + offset.x = loc.x - end_loc.x; + offset.y = loc.z - end_loc.z; + + F32 len2 = offset.length2(); + + if (len2 < 0.001f) + { + owner.move.dest.x = end_loc.x; + owner.move.dest.z = end_loc.z + tweak.orbit_radius; + } + else + { + F32 scale = tweak.orbit_radius / xsqrt(len2); + + owner.move.dest.x = offset.x * scale + end_loc.x; + owner.move.dest.z = offset.y * scale + end_loc.z; + } + + owner.move.dest.y = end_loc.y; + + owner.dissolve(0.0f); + owner.face_player(); + + owner.flag.move = zNPCDutchman::MOVE_FOLLOW; + owner.turn.vel = tweak.initiate.turn_vel; + owner.turn.accel = tweak.initiate.turn_accel; + owner.move.vel.assign(0.0f, tweak.initiate.up_vel, 0.0f); + + return zNPCGoalCommon::Enter(dt, updCtxt); +} + +S32 zNPCGoalDutchmanInitiate::Exit(F32 dt, void* updCtxt) +{ + owner.turn.accel = tweak.turn_accel; + + return xGoal::Exit(dt, updCtxt); +} + +S32 zNPCGoalDutchmanInitiate::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) +{ + if (owner.move.vel.length2() < 0.01f && + (owner.move.dest - owner.get_center()).length2() < 0.01f && !owner.turning(0.2f)) + { + *trantype = GOAL_TRAN_SET; + return NPC_GOAL_DUTCHMANREAPPEAR; + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +xFactoryInst* zNPCGoalDutchmanIdle::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanIdle(who, (zNPCDutchman&)*info); +} + +S32 zNPCGoalDutchmanIdle::Enter(F32 dt, void* updCtxt) +{ + owner.face_player(); + return zNPCGoalCommon::Enter(dt, updCtxt); +} + +S32 zNPCGoalDutchmanIdle::Exit(F32 dt, void* updCtxt) +{ + return xGoal::Exit(dt, updCtxt); +} + +S32 zNPCGoalDutchmanIdle::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) +{ + if (owner.delay >= owner.goal_delay()) + { + *trantype = GOAL_TRAN_SET; + return owner.next_goal(); + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +xFactoryInst* zNPCGoalDutchmanDisappear::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanDisappear(who, (zNPCDutchman&)*info); +} + +S32 zNPCGoalDutchmanDisappear::Enter(F32 dt, void* updCtxt) +{ + owner.delay = 0.0f; + owner.dissolve(tweak.teleport.fade_time); + + return zNPCGoalCommon::Enter(dt, updCtxt); +} + +S32 zNPCGoalDutchmanDisappear::Exit(F32 dt, void* updCtxt) +{ + return xGoal::Exit(dt, updCtxt); +} + +S32 zNPCGoalDutchmanDisappear::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) +{ + if (owner.delay >= tweak.teleport.fade_time) + { + *trantype = GOAL_TRAN_SET; + return NPC_GOAL_DUTCHMANTELEPORT; + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +xFactoryInst* zNPCGoalDutchmanTeleport::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanTeleport(who, (zNPCDutchman&)*info); +} + +S32 zNPCGoalDutchmanTeleport::Enter(F32 dt, void* updCtxt) +{ + owner.turn.accel = tweak.teleport.turn_accel; + owner.turn.max_vel = tweak.teleport.turn_max_vel; + owner.move.accel = tweak.teleport.accel; + owner.move.max_vel = tweak.teleport.max_vel; + owner.move.dest = owner.random_orbit(owner.get_center(), PI / 2.0f, PI); + + owner.turn_to_face(owner.move.dest); + + owner.flag.move = zNPCDutchman::MOVE_FOLLOW; + + return zNPCGoalCommon::Enter(dt, updCtxt); +} + +S32 zNPCGoalDutchmanTeleport::Exit(F32 dt, void* updCtxt) +{ + return xGoal::Exit(dt, updCtxt); +} + +S32 zNPCGoalDutchmanTeleport::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) +{ + if (owner.move.vel.length2() < 0.01f && + (owner.move.dest - owner.get_center()).length2() < 0.01f) + { + *trantype = GOAL_TRAN_SET; + return NPC_GOAL_DUTCHMANREAPPEAR; + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +xFactoryInst* zNPCGoalDutchmanReappear::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanReappear(who, (zNPCDutchman&)*info); +} + +S32 zNPCGoalDutchmanReappear::Enter(F32 dt, void* updCtxt) +{ + owner.delay = 0.0f; + + owner.face_player(); + owner.coalesce(tweak.teleport.fade_time); + + return zNPCGoalCommon::Enter(dt, updCtxt); +} + +S32 zNPCGoalDutchmanReappear::Exit(F32 dt, void* updCtxt) +{ + owner.reset_speed(); + + return xGoal::Exit(dt, updCtxt); +} + +S32 zNPCGoalDutchmanReappear::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) +{ + xAnimState* anim = owner.AnimCurState(); + + if (anim->ID != g_hash_subbanim[5] || dt > owner.AnimTimeRemain(NULL)) + { + *trantype = GOAL_TRAN_SET; + return owner.next_goal(); + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +xFactoryInst* zNPCGoalDutchmanBeam::create(S32 who, RyzMemGrow* grow, void* info) +{ + return new (who, grow) zNPCGoalDutchmanBeam(who, (zNPCDutchman&)*info); +} + +S32 zNPCGoalDutchmanBeam::Enter(F32 dt, void* updCtxt) +{ + substate = SS_STOP; + shots = 0; + + xVec3 player_loc; + + zEntPlayer_PredictPos(&player_loc, tweak.beam.focus_time, 1.0f, 1); + + owner.turn_to_face(player_loc); + + return zNPCGoalCommon::Enter(dt, updCtxt); +} + +S32 zNPCGoalDutchmanBeam::Exit(F32 dt, void* updCtxt) +{ + return xGoal::Exit(dt, updCtxt); +} + +S32 zNPCGoalDutchmanBeam::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) +{ + switch (substate) + { + case SS_STOP: + update_stop(dt); + break; + case SS_FOCUS: + update_focus(dt); + break; + case SS_FIRE: + update_fire(dt); + break; + case SS_UNFOCUS: + update_unfocus(dt); + break; + case SS_DONE: + *trantype = GOAL_TRAN_SET; + return owner.next_goal(); + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +void zNPCGoalDutchmanBeam::update_stop(F32 dt) { - // Was going to try this function, but literally can't find 0x1d9 - if (toEvent == 0x1d9) - { - psy_instinct->GoalSet('NGM=', 1); - } - else + if (!owner.turning()) { - if ((0x1d8 < toEvent) || (toEvent != 0x1b5)) + substate = SS_FOCUS; + + owner.delay = 0.0f; + owner.beam[0].alpha = 0.0f; + owner.beam[0].thickness = tweak.beam.thickness; + beam[0].ribbon_flags = 1; + owner.beam[1].alpha = 0.0f; + owner.beam[1].thickness = tweak.beam.thickness; + beam[1].ribbon_flags = 1; + + owner.start_beam(); + + for (S32 i = 0; i < 2; i++) { - handled = 0; - return zNPCCommon::SysEvent(from, to, toEvent, toParam, toParamWidget, handled); + beam[i].impact_sound = play_sound(0, &beam[i].loc, owner.beam[i].alpha); + beam[i].glow_sound = play_sound(3, &owner.beam[i].start_loc, 1.0f); } - start_fight(); } - return 1; } -void zNPCDutchman::Render() +void zNPCGoalDutchmanBeam::update_focus(F32 dt) { - zNPCDutchman::render_debug(); -} + xVec3 target; -void zNPCDutchman::RenderExtra() -{ - S32 oldzwrite; - S32 oldztest; - U32 oldsrcblend; - U32 olddestblend; - U8 oldcmp; - xModelInstance* m; - U8 haloing; - - RwRenderStateGet(rwRENDERSTATEFOGENABLE, (void*)&oldcmp); - RwRenderStateGet(rwRENDERSTATESRCBLEND, (void*)&oldcmp); - RwRenderStateGet(rwRENDERSTATEDESTBLEND, (void*)&oldcmp); - RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)&oldcmp); - iDrawSetFBMSK(-1); - - for (m = model; m != 0; m = m) - { - xModelRenderSingle(m); - } + predict_target(target); - iDrawSetFBMSK(0); + owner.turn_to_face(target); + + aim_beam(beam[0], target, 0.0f); + refresh_beam(0); + aim_beam(beam[1], target, PI); + refresh_beam(1); - for (m = model; m != 0; m = m) + F32 frac = owner.delay / tweak.beam.focus_time; + F32 frac2 = frac * frac; + + if (frac >= 1.0f) { - xModelRenderSingle(m); + owner.beam[0].alpha = 1.0f; + owner.beam[1].alpha = 1.0f; + owner.delay = 0.0f; + + substate = SS_FIRE; + } + else + { + owner.beam[0].alpha = frac2; + owner.beam[1].alpha = frac2; + + set_volume(0, beam[0].impact_sound, owner.beam[0].alpha); + set_volume(0, beam[1].impact_sound, owner.beam[1].alpha); } +} - oldcmp = FALSE; +void zNPCGoalDutchmanBeam::update_fire(F32 dt) +{ + update_beam(dt, beam[0], 0); + update_beam(dt, beam[1], 1); - if (flag.beaming != 0) + if (beam[0].vel >= -0.00001f && beam[0].vel <= 0.00001f && + xabs(beam[0].dist / tweak.beam.end_dist - 1.0f) <= 0.00001f && beam[1].vel >= -0.00001f && + beam[1].vel <= 0.00001f && xabs(beam[1].dist / tweak.beam.end_dist - 1.0f) <= 0.00001f) { - if (beam->segments != 0) + owner.delay = 0.0f; + substate = SS_UNFOCUS; + + for (S32 i = 0; i < 2; i++) { - oldcmp = TRUE; + kill_sound(3, beam[i].glow_sound); } - render_beam(); - // if (*(int*)(this + 0x430) + *(int*)(this + 0x54c) != 0) - // { - // bVar1 = true; - // } } - // 0x2c0 - oldzwrite = flag.fade; - if (oldcmp) +} + +void zNPCGoalDutchmanBeam::update_unfocus(F32 dt) +{ + F32 frac = 1.0f - owner.delay / tweak.beam.focus_time; + F32 frac2 = frac * frac; + + if (frac <= 0.0f) { - render_beam(); + owner.stop_beam(); + + shots++; + + substate = (shots < tweak.beam.shots[owner.round]) ? SS_STOP : SS_DONE; + + kill_sound(0, beam[0].impact_sound); + kill_sound(0, beam[1].impact_sound); } - if ((2U - oldzwrite | oldzwrite - 2U) < 0) + else { - render_halo(); + owner.beam[0].alpha = frac2; + owner.beam[1].alpha = frac2; + + set_volume(0, beam[0].impact_sound, owner.beam[0].alpha); + set_volume(0, beam[1].impact_sound, owner.beam[1].alpha); } - RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)&oldcmp); - RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)&oldcmp); } -void zNPCDutchman::ParseINI() +void zNPCGoalDutchmanBeam::aim_beam(beam_data& data, const xVec3& target, F32 offset) const { - zNPCCommon::ParseINI(); - cfg_npc->snd_traxShare = g_sndTrax_Dutchman; - NPCS_SndTablePrepare(g_sndTrax_Dutchman); - cfg_npc->snd_trax = g_sndTrax_Dutchman; - NPCS_SndTablePrepare(g_sndTrax_Dutchman); - tweak.load(parmdata, pdatsize); -} + const tweak_group& tw = tweak; + const xVec3& center = owner.get_center(); -void zNPCDutchman::SelfSetup() -{ - xBehaveMgr* bmgr = xBehaveMgr_GetSelf(); - this->psy_instinct = bmgr->Subscribe(this, 0); - xPsyche* psy = this->psy_instinct; - psy->BrainBegin(); - S32 i; - for (i = NPC_GOAL_DUTCHMANNIL; i <= NPC_GOAL_DUTCHMANDEATH; i++) + data.origin.assign(center.x, center.z); + data.dir.assign(target.x - center.x, target.z - center.z); + + F32 len2 = data.dir.length2(); + + if (len2 >= -0.00001f && len2 <= 0.00001f) { - psy->AddGoal(i, this); + data.dir.assign(1.0f, 0.0f); } - psy->BrainEnd(); - psy->SetSafety(NPC_GOAL_DUTCHMANIDLE); + else + { + data.dir *= 1.0f / xsqrt(len2); + } + + data.dist = tw.beam.start_dist; + data.vel = 0.0f; + data.wave_offset = offset; } -void zNPCDutchman::Damage(en_NPC_DAMAGE_TYPE, xBase*, const xVec3*) +void zNPCGoalDutchmanBeam::calc_beam_loc(xVec2& loc, F32 dist, const beam_data& data) const { - xPsyche* psy = this->psy_instinct; - psy->GIDOfActive(); + const beam_config& cfg = tweak.beam; + F32 frac = (dist - cfg.start_dist) / (cfg.end_dist - cfg.start_dist); + F32 wave_mag = frac * (cfg.wave_max - cfg.wave_min) + cfg.wave_min; + F32 wave_offset = wave_mag * isin(2.0f * PI * (frac * cfg.wave_freq) + data.wave_offset); + xVec2 dir_tan = { 0.0f, 0.0f }; + + dir_tan.x = data.dir.y; + dir_tan.y = -data.dir.x; + + loc = (data.origin + data.dir * dist) + dir_tan * wave_offset; } -U32 zNPCDutchman::AnimPick(S32 rawgoal, en_NPC_GOAL_SPOT gspot, xGoal* goal) +void zNPCGoalDutchmanBeam::update_beam(F32 dt, beam_data& beam, S32 which) { - S32 index = -1; - U32 animId = 0; + const beam_config& cfg = tweak.beam; - switch (rawgoal) + start_effects(which, dt); + + F32 dist = beam.dist; + F32 vel = beam.vel; + + xAccelMove(dist, vel, cfg.accel, dt, cfg.end_dist, cfg.max_vel); + + F32 diff = dist - beam.dist; + F32 ddt = dt; + + if (diff >= 1.5f * tweak.beam.segment_width) { - case NPC_GOAL_DUTCHMANNIL: - case NPC_GOAL_DUTCHMANDAMAGE: - case NPC_GOAL_DUTCHMANDEATH: - index = -1; - break; - case NPC_GOAL_DUTCHMANINITIATE: - case NPC_GOAL_DUTCHMANIDLE: - case NPC_GOAL_DUTCHMANDISAPPEAR: - case NPC_GOAL_DUTCHMANTELEPORT: - index = 1; - break; - case NPC_GOAL_DUTCHMANREAPPEAR: - index = 5; - break; - case NPC_GOAL_DUTCHMANBEAM: - index = 0xC; - break; - case NPC_GOAL_DUTCHMANFLAME: - index = 0x10; - break; - case NPC_GOAL_DUTCHMANPOSTFLAME: - if (flag.hurting != false) + S32 iterations = (S32)(0.5f + diff / tweak.beam.segment_width) - 1; + + if (iterations > 15) { - index = 4; + iterations = 15; } - else + + F32 d = 1.0f / (iterations + 1); + F32 ddist = d * diff; + + ddt = d * ddt; + + for (S32 i = 0; i < iterations; i++) { - index = 6; - } - break; - case NPC_GOAL_DUTCHMANCAUGHT: - index = 0x13; - break; + beam.dist += ddist; - default: - index = 1; - break; + refresh_beam(which); + add_effects(which, ddt); + } } - if (index > -1) - { - animId = g_hash_subbanim[index]; - } + beam.dist = dist; + beam.vel = vel; - return animId; + refresh_beam(which); + add_effects(which, ddt); } -void zNPCDutchman::LassoNotify(en_LASSO_EVENT event) +void zNPCGoalDutchmanBeam::refresh_beam(S32 which) { - if ((event != 3) && (event < 3) && (event > 1)) - { - // xPsyche::GoalSet(NPC_GOAL_DUTCHMANCAUGHT, 1); - } - - zNPCCommon::LassoNotify(event); -} -//NPC_GOAL_DUTCHMANCAUGHT + xVec2 loc; -// double zNPCDutchman::goal_delay() -// { -// move_info* tempR4; -// tempR4 = &this->move; -// move = *tempR4; -// } + calc_beam_loc(loc, beam[which].dist, beam[which]); -S32 zNPCDutchman::LassoSetup() -{ - zNPCCommon::LassoUseGuides(1, 1); - return zNPCCommon::LassoSetup(); + beam[which].loc.assign(loc.x, tweak.ground_y, loc.y); } -void zNPCDutchman::update_round() +void zNPCGoalDutchmanBeam::start_effects(S32 which, F32 dt) { - S32 roundCntr = round; - if (life == 0) - { - round = 3; - } - else - { - round = 2 - ((life + -1) * 3) / 3; - } - if (round == roundCntr) - { - return; - } + owner.beam[which].start_loc = owner.get_eye_loc(which); + owner.beam[which].segments = 0; - stage = -1; + xParEmitterAsset& ea = *eyeglow_emitter[which]->tasset; + + ea.pos = owner.beam[which].start_loc; + ea.pos.x += tweak.beam.glow_dist * beam[which].dir.x; + ea.pos.z += tweak.beam.glow_dist * beam[which].dir.y; + + owner.emit_particles(*eyeglow_emitter[which], dt); } -S32 zNPCDutchman::next_goal() +void zNPCGoalDutchmanBeam::add_miss_effects(S32 which, F32 dt) { - stage++; + beam[which].ribbon_flags = 1; - if (sequence + (round << 7) + (stage * 8) == 0) - { - stage = 0; - } - delay = 0.0f; - return (S32)sequence + (round * 128) + (stage * 8); + zNPCDutchman::beam_end& end = owner.beam[which].end[owner.beam[which].segments]; + + end.extend = TRUE; + end.loc = beam[which].loc; + + owner.beam[which].segments++; } -S32 zNPCDutchman::goal_delay() +void zNPCGoalDutchmanBeam::add_blast_effects(S32 which, F32 dt) { - return (S32)sequence + (round * 128) + (stage * 8 + 4); + zNPCDutchman::beam_end& end = owner.beam[which].end[owner.beam[which].segments]; + + end.extend = FALSE; + end.loc = beam[which].loc; + + owner.beam[which].segments++; + + eye_scorch[which].insert(beam[which].loc, 0.0f, 1.0f, 1.0f, beam[which].ribbon_flags); + + beam[which].ribbon_flags = 0; + + plasma_emitter_settings.pos = beam[which].loc; + owner.emit_particles(*plasma_emitter, dt, plasma_emitter_settings); + + spark_emitter_settings.pos = beam[which].loc; + owner.emit_particles(*spark_emitter, dt, spark_emitter_settings); + + light_emitter_settings.pos = beam[which].loc; + light_emitter_settings.rate.set((2.0f / FRAME_TIME) * tweak.beam.light_rate); + owner.emit_particles(*light_emitter, FRAME_TIME, light_emitter_settings); } -void zNPCDutchman::decompose() +void zNPCGoalDutchmanBeam::add_effects(S32 which, F32 dt) { - if (flag.fighting != 0) + xVec3 offset = beam[which].loc - owner.get_orbit(); + F32 max_dist = 0.01f + tweak.ground_radius; + + if (offset.length2() > max_dist * max_dist) { - flag.fighting = 0; - disable_emitter(*dissolve_emitter); - disable_emitter(*fadein_emitter); - disable_emitter(*fadeout_emitter); - zCameraEnableTracking(CO_BOSS); - boss_cam.stop; + add_miss_effects(which, dt); } -} - -namespace -{ - void set_yaw_matrix(xMat3x3& mat, F32 dt) + else { - F32 tempSin; - F32 tempCos; - - tempSin = isin(dt); - tempCos = icos(dt); - mat.right.assign(tempCos, 0.0f, -tempSin); - mat.up.assign(0.0f, 1.0f, 0.0f); - mat.at.assign(tempSin, 0.0f, tempCos); + add_blast_effects(which, dt); } -} // namespace - -void zNPCDutchman::render_debug() -{ } -void zNPCDutchman::update_animation(float) +void zNPCGoalDutchmanBeam::predict_target(xVec3& target) const { -} + const xVec3& player_loc = (xVec3&)globals.player.ent.model->Mat->pos; + xVec3 offset = player_loc - owner.get_center(); + F32 len2 = offset.length2(); + F32 minmag = tweak.beam.start_dist; + F32 limit = minmag * minmag; -void zNPCDutchman::update_camera(F32 dt) -{ - zCameraDisableTracking(CO_BOSS); - if ((zCameraIsTrackingDisabled() & -9) == 0) + if (len2 <= 0.001f + limit) { - boss_cam.update(dt); + target = player_loc; } -} + else + { + F32 t = xAccelMoveTime(xsqrt(len2) - minmag, tweak.beam.accel, tweak.beam.end_dist, + tweak.beam.max_vel); -void zNPCDutchman::kill_wave(zNPCDutchman::wave_data& wave) -{ - kill_sound(1, wave.sound_handle); + zEntPlayer_PredictPos(&target, t, 1.0f, 1); + } } -void zNPCDutchman::start_eye_glow() +xFactoryInst* zNPCGoalDutchmanFlame::create(S32 who, RyzMemGrow* grow, void* info) { - flag.eye_glow = true; - eye_glow.size = 1; + return new (who, grow) zNPCGoalDutchmanFlame(who, (zNPCDutchman&)*info); } -void zNPCDutchman::stop_eye_glow() +S32 zNPCGoalDutchmanFlame::Enter(F32 dt, void* updCtxt) { - flag.eye_glow = false; -} + owner.reset_lasso_anim(); + owner.turn_to_face(owner.get_orbit()); -void zNPCDutchman::start_hand_trail() -{ - flag.hand_trail = true; - for (S32 i = 0; i < 2; i++) - { - get_hand_loc(i); - hand_trail.loc[i] = hand_trail.loc[i]; - } + owner.delay = 0.0f; + substate = SS_WAIT; - // hand_trail.loc[0] // 0x5cc + return zNPCGoalCommon::Enter(dt, updCtxt); } -void zNPCDutchman::stop_hand_trail() +S32 zNPCGoalDutchmanFlame::Exit(F32 dt, void* updCtxt) { - flag.hand_trail = false; + owner.flg_vuln &= ~0x1000000; + + owner.stop_flames(); + owner.stop_hand_trail(); + + return xGoal::Exit(dt, updCtxt); } -void zNPCDutchman::dissolve(F32 delay) +S32 zNPCGoalDutchmanFlame::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) { - F32 volume; - if (delay == 0.0f) - { - flag.fade = FADE_TELEPORT; - disable_emitter(*fadeout_emitter); - set_alpha(0.0f); - vanish(); - volume = 1.0f; - } - else + switch (substate) { - flag.fade = FADE_DISSOLVE; - fade.time = 0.0f; - fade.duration = delay; - fade.iduration = 1.0f / fade.duration; - enable_emitter(*fadeout_emitter); - set_alpha(1.0f); - reappear(); - volume = 0.0f; + case SS_WAIT: + update_wait(dt); + break; + case SS_MOVE: + update_move(dt); + break; + case SS_STOP: + update_stop(dt); + break; + case SS_DONE: + *trantype = GOAL_TRAN_SET; + return owner.next_goal(); } - enable_emitter(*dissolve_emitter); - start_eye_glow(); - start_hand_trail(); - if (fade.sound_handle == 0) + return xGoal::Process(trantype, dt, updCtxt, xscn); +} + +void zNPCGoalDutchmanFlame::update_wait(F32 dt) +{ + if (owner.delay < tweak.flame.start_delay) { - fade.sound_handle = play_sound(2, (const xVec3*)&bound.pad[3], volume); + return; } - else + + if (!owner.turning()) { - set_volume(2, 0, 0.0f); + substate = SS_MOVE; + + owner.move.accel = tweak.accel; + owner.move.max_vel = tweak.max_vel; + + const xVec3& orbit = owner.get_orbit(); + xVec3& dest = owner.move.dest; + + dest = orbit - (dest - orbit); + + move_dir.assign(dest.x - orbit.x, dest.z - orbit.z); + move_dir.normalize(); + + owner.flag.move = zNPCDutchman::MOVE_FOLLOW; + stopped = FALSE; + + owner.turn_to_face(dest); + owner.start_flames(); + owner.start_hand_trail(); } } -void zNPCDutchman::coalesce(F32 delay) +void zNPCGoalDutchmanFlame::update_move(F32 dt) { - reappear(); - if (delay <= 0.0f) + if (!stopped) { - flag.fade = FADE_NONE; - disable_emitter(*fadein_emitter); - disable_emitter(*dissolve_emitter); - set_alpha(1.0f); - stop_eye_glow(); - stop_hand_trail(); - if (fade.sound_handle != 0) + xVec3 splash_loc = owner.get_splash_loc(); + const xVec3& orbit = owner.get_orbit(); + xVec2 orbit_offset = { 0.0f, 0.0f }; + + orbit_offset.x = splash_loc.x - orbit.x; + orbit_offset.y = splash_loc.z - orbit.z; + + if (orbit_offset.dot(move_dir) > tweak.ground_radius) { - kill_sound(2, fade.sound_handle); - fade.sound_handle = 0; + owner.AnimStart(g_hash_subbanim[ANIM_Attack02End01], 0); + owner.flg_vuln &= ~0x1000000; + + owner.stop_flames(); + owner.stop_hand_trail(); + + owner.move.accel = tweak.teleport.accel; + owner.move.max_vel = tweak.teleport.max_vel; + + stopped = TRUE; } - } - else - { - flag.fade = FADE_COALESCE; - fade.time = 0.0f; - fade.duration = delay; - fade.iduration = 1.0f / fade.duration; - enable_emitter(*fadein_emitter); - enable_emitter(*dissolve_emitter); - set_alpha(0.0f); - start_eye_glow(); - start_hand_trail(); - if (fade.sound_handle != 0) + else { - set_volume(2, fade.sound_handle, 1.0f); + refresh_vulnerability(); } } -} - -void zNPCDutchman::reset_blob_mat() -{ - // Decomp.me says 90% - F32 temp; - F32 temp2; - temp = isin(tweak.flame.blob_pitch); - temp2 = icos(tweak.flame.blob_pitch); - flames.blob_mat.right.assign(1.0f, 0.0f, 0.0f); - flames.blob_mat.up.assign(0.0f, temp2, temp); - flames.blob_mat.at.assign(0.0f, -temp, temp2); -} + if (owner.move.vel.length2() < 0.01f && + (owner.move.dest - owner.get_center()).length2() < 0.01f) + { + substate = SS_STOP; -void zNPCDutchman::reset_lasso_anim() -{ - xAnimPlaySetState(0, lassdata->holdGuideAnim, 0); -} + owner.face_player(); -void zNPCDutchman::update_fade(F32 delay) -{ - F32 frac; - if (flag.fade != FADE_TELEPORT) - { - if (flag.fade < 1) - { - if (flag.fade < 4) - { - fade.time = fade.time + delay; - if (fade.time >= fade.duration) - { - flag.fade = FADE_TELEPORT; - disable_emitter(*fadeout_emitter); - set_alpha(0.0f); - vanish(); - set_volume(2, fade.sound_handle, 1.0f); - } - else - { - frac = fade.time * fade.iduration; - set_alpha(1.0f - frac); - set_volume(2, fade.sound_handle, frac); - } - } - } - else if (flag.fade < 4) + if (!stopped) { - fade.time = fade.time + delay; - if (fade.time >= fade.duration) - { - flag.fade = FADE_NONE; - disable_emitter(*fadein_emitter); - disable_emitter(*dissolve_emitter); - set_alpha(1.0f); - stop_eye_glow(); - stop_hand_trail(); - reappear(); - kill_sound(2, fade.sound_handle); - fade.sound_handle = 0; - } - else - { - frac = fade.time * fade.iduration; - set_alpha(frac); - set_volume(2, fade.sound_handle, 1.0f - frac); - } + owner.AnimStart(g_hash_subbanim[ANIM_Attack02End01], 0); + owner.flg_vuln &= ~0x1000000; + + owner.stop_flames(); + owner.stop_hand_trail(); } } } -void zNPCDutchman::add_splash(const xVec3&, float) +void zNPCGoalDutchmanFlame::update_stop(F32 dt) { -} + U8 anim_done = FALSE; -void zNPCDutchman::start_fight() -{ - if (flag.fighting == 0 && life <= 0) + if (g_hash_subbanim[ANIM_Attack02End01] != owner.AnimCurState()->ID || + dt > owner.AnimTimeRemain(NULL)) { - flag.fighting = 1; - psy_instinct->GoalSet(0, 0); - zCameraDisableTracking(CO_BOSS); - boss_cam.start(globals.camera); - boss_cam.set_targets((xVec3&)globals.player.ent.model->Mat->pos, (xVec3&)model->Mat->pos, - 2.0f); + anim_done = TRUE; } -} -void zNPCDutchman::set_life(S32 lf) -{ - life = range_limit(life, 0, 3); - if (life < lf) + if (anim_done && !owner.turning(0.1f) && + (owner.move.dest - owner.get_center()).length2() < 0.01f) { - flag.hurting = 1; - for (S32 i = life; i < life; i++) - { - zEntEvent((xBase*)lf, (xBase*)lf, 0x1d7); // Haven't found 0x1d7. only 0x1d8 and 0x1d4 - } + substate = SS_DONE; + owner.flag.face_player = false; } } -void zNPCDutchman::start_beam() +void zNPCGoalDutchmanFlame::refresh_vulnerability() { - if ((flag.beaming) != 0) + const xVec3& loc = owner.get_center(); + F32 orbit_dist2 = (loc - owner.get_orbit()).length2(); + F32 max_orbit_dist = tweak.orbit_radius - tweak.lasso.safety_dist; + + if (orbit_dist2 >= max_orbit_dist * max_orbit_dist) { + owner.flg_vuln &= ~0x1000000; return; } - flag.beaming = 1; - flag.was_beaming = 0; - beam[1].segments = 0; //0x54C - beam[0].segments = 0; //0x430 - //Could also write as: + const xVec3& player_loc = (xVec3&)globals.player.ent.model->Mat->pos; + xVec3 player_offset = loc - player_loc; + F32 player_dist2 = player_offset.length2(); - //for (S32 i = 1; i >= 0; --i) - // { - // beam[i].segments = 0; - // } -} + if (owner.flg_vuln & 0x1000000) + { + F32 min_dist2 = tweak.lasso.min_dist_disable * tweak.lasso.min_dist_disable; -void zNPCDutchman::stop_beam() -{ - flag.beaming = false; -} + if (player_dist2 < min_dist2) + { + owner.flg_vuln &= ~0x1000000; + return; + } -void zNPCDutchman::start_flames() -{ - //static_queue::clear(); - flag.flaming = true; - flames.time = 0.0; - flames.emitted = 0; - flames.blob_break = 1; - flames.splash_break = 1; - slime.slices.clear(); -} + F32 ang = angle_difference(owner.turn.dir, xVec2::create(player_offset.x, player_offset.z)); -void zNPCDutchman::stop_flames() -{ - flag.flaming = false; -} + if (ang > tweak.lasso.max_angle_disable) + { + owner.flg_vuln &= ~0x1000000; + } + } + else + { + F32 min_dist2 = tweak.lasso.min_dist_enable * tweak.lasso.min_dist_enable; -void zNPCDutchman::get_eye_loc(S32 index) const -{ - S32 lookup = 0; - xModelGetBoneLocation(*model, lookup + index * 4); -} + if (player_dist2 >= min_dist2) + { + F32 ang = + angle_difference(owner.turn.dir, xVec2::create(player_offset.x, player_offset.z)); -void zNPCDutchman::vanish() -{ - old.moreFlags = moreFlags; - pflags = 0; - moreFlags = 0; - flags2.flg_colCheck = 0; - flags2.flg_penCheck = 0; - chkby = 0; - penby = 0; - xEntHide(this); + if (xabs(ang) <= tweak.lasso.max_angle_enable) + { + owner.flg_vuln |= 0x1000000; + } + } + } } -void zNPCDutchman::reappear() +namespace { - moreFlags = old.moreFlags; - xNPCBasic::RestoreColFlags(); - xEntShow(this); -} + F32 angle_difference(const xVec2& a, const xVec2& b) + { + F32 base = xatan2(a.x, a.y); -void zNPCDutchman::reset_speed() -{ -} + return xrmod(PI + (xatan2(b.x, b.y) - base)) - PI; + } +} // namespace -xFactoryInst* zNPCGoalDutchmanInitiate::create(S32 who, RyzMemGrow* grow, void* info) +xFactoryInst* zNPCGoalDutchmanPostFlame::create(S32 who, RyzMemGrow* grow, void* info) { - return new (who, grow) zNPCGoalDutchmanInitiate(who, (zNPCDutchman&)*info); + return new (who, grow) zNPCGoalDutchmanPostFlame(who, (zNPCDutchman&)*info); } -S32 zNPCGoalDutchmanInitiate::Enter(F32 dt, void* updCtxt) +S32 zNPCGoalDutchmanPostFlame::Enter(F32 dt, void* updCtxt) { - owner.get_orbit(); - owner.nav_curr->PosGet(); + if (owner.life <= 0.0f) + { + if (globals.player.Health != 0 && !(globals.player.ControlOff & 4)) + { + zEntEvent(&owner, &owner, eEventDeath); + } + } + + owner.delay = 0.0f; return zNPCGoalCommon::Enter(dt, updCtxt); } -S32 zNPCGoalDutchmanInitiate::Exit(F32 dt, void* updCtxt) +S32 zNPCGoalDutchmanPostFlame::Exit(F32 dt, void* updCtxt) { + owner.flag.hurting = false; return xGoal::Exit(dt, updCtxt); } -xFactoryInst* zNPCGoalDutchmanIdle::create(S32 who, RyzMemGrow* grow, void* info) +S32 zNPCGoalDutchmanPostFlame::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) { - return new (who, grow) zNPCGoalDutchmanIdle(who, (zNPCDutchman&)*info); -} + xAnimState* anim = owner.AnimCurState(); -S32 zNPCGoalDutchmanIdle::Enter(F32 dt, void* updCtxt) -{ - owner.face_player(); - return zNPCGoalCommon::Enter(dt, updCtxt); -} + if (anim->ID == g_hash_subbanim[ANIM_Idle01]) + { + owner.update_round(); -S32 zNPCGoalDutchmanIdle::Exit(F32 dt, void* updCtxt) -{ - return xGoal::Exit(dt, updCtxt); -} + *trantype = GOAL_TRAN_SET; -S32 zNPCGoalDutchmanIdle::Process(en_trantype* trantype, float dt, void* updCtxt, xScene* xscn) -{ - owner.goal_delay(); - if (owner.delay == owner.delay) - { - owner.delay = 1; + if (owner.life <= 0) + { + return NPC_GOAL_DUTCHMANDEATH; + } - owner.next_goal(); + return owner.next_goal(); } - else + + if (owner.flag.hurting && owner.delay >= tweak.wipe.delay && + owner.delay <= tweak.wipe.delay + tweak.wipe.duration) { - xGoal::Process(trantype, dt, updCtxt, xscn); + owner.get_facing(); + + xVec3 emit_loc = owner.get_hand_loc(1); + + emit_loc.y += tweak.wipe.yoffset; + + snot_emitter_settings.pos = emit_loc; + snot_emitter_settings.vel = 0.0f; + + xParEmitterPropsAsset& prop = *snot_emitter->prop; + F32 old_rate[2] = { 0.0f, 0.0f }; + + old_rate[0] = prop.rate.val[0]; + old_rate[1] = prop.rate.val[1]; + + prop.rate.val[0] *= tweak.wipe.rate_mult; + prop.rate.val[1] *= tweak.wipe.rate_mult; + + owner.emit_particles(*snot_emitter, dt, snot_emitter_settings); + + prop.rate.val[0] = old_rate[0]; + prop.rate.val[1] = old_rate[1]; } - return 0; - //return xGoal::Process(trantype, dt, updCtxt, xscn); + return xGoal::Process(trantype, dt, updCtxt, xscn); } -xFactoryInst* zNPCGoalDutchmanDisappear::create(S32 who, RyzMemGrow* grow, void* info) +xFactoryInst* zNPCGoalDutchmanCaught::create(S32 who, RyzMemGrow* grow, void* info) { - return new (who, grow) zNPCGoalDutchmanDisappear(who, (zNPCDutchman&)*info); + return new (who, grow) zNPCGoalDutchmanCaught(who, (zNPCDutchman&)*info); } -S32 zNPCGoalDutchmanDisappear::Exit(F32 dt, void* updCtxt) +S32 zNPCGoalDutchmanCaught::Enter(F32 dt, void* updCtxt) { - return xGoal::Exit(dt, updCtxt); -} + owner.delay = 0.0f; + owner.halt(tweak.lasso.decel); -xFactoryInst* zNPCGoalDutchmanDamage::create(S32 who, RyzMemGrow* grow, void* info) -{ - return new (who, grow) zNPCGoalDutchmanDamage(who, (zNPCDutchman&)*info); + play_sound(4, &owner.bound.sph.center, 1.0f); + + return zNPCGoalCommon::Enter(dt, updCtxt); } -S32 zNPCGoalDutchmanDamage::Exit(F32 dt, void* updCtxt) +S32 zNPCGoalDutchmanCaught::Exit(F32 dt, void* updCtxt) { return xGoal::Exit(dt, updCtxt); } -xFactoryInst* zNPCGoalDutchmanTeleport::create(S32 who, RyzMemGrow* grow, void* info) +S32 zNPCGoalDutchmanCaught::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) { - return new (who, grow) zNPCGoalDutchmanTeleport(who, (zNPCDutchman&)*info); -} + owner.LassoSyncAnims(LASS_ANIM_GRAB); -S32 zNPCGoalDutchmanTeleport::Exit(F32 dt, void* updCtxt) -{ - return xGoal::Exit(dt, updCtxt); -} + xAnimState* anim = owner.AnimCurState(); -xFactoryInst* zNPCGoalDutchmanReappear::create(S32 who, RyzMemGrow* grow, void* info) -{ - return new (who, grow) zNPCGoalDutchmanReappear(who, (zNPCDutchman&)*info); -} + if (owner.delay >= tweak.lasso.escape_delay || anim->ID != g_hash_subbanim[ANIM_LassoGrab01] || + dt > owner.AnimTimeRemain(NULL)) + { + owner.set_life(owner.life - 1); -S32 zNPCGoalDutchmanReappear::Exit(F32 dt, void* updCtxt) -{ - owner.reset_speed(); - return xGoal::Exit(dt, updCtxt); -} + *trantype = GOAL_TRAN_SET; + return NPC_GOAL_DUTCHMANDAMAGE; + } -xFactoryInst* zNPCGoalDutchmanBeam::create(S32 who, RyzMemGrow* grow, void* info) -{ - return new (who, grow) zNPCGoalDutchmanBeam(who, (zNPCDutchman&)*info); -} + owner.flag.face_player = false; -S32 zNPCGoalDutchmanBeam::Exit(F32 dt, void* updCtxt) -{ - return xGoal::Exit(dt, updCtxt); -} + const xVec3& player_loc = (xVec3&)globals.player.ent.model->Mat->pos; + const xVec3& center = owner.get_center(); -xFactoryInst* zNPCGoalDutchmanFlame::create(S32 who, RyzMemGrow* grow, void* info) -{ - return new (who, grow) zNPCGoalDutchmanFlame(who, (zNPCDutchman&)*info); + owner.turn.dir.assign(center.x - player_loc.x, center.z - player_loc.z); + owner.turn.dir.normalize(); + + return xGoal::Process(trantype, dt, updCtxt, xscn); } -S32 zNPCGoalDutchmanFlame::Enter(F32 dt, void* updCtxt) +xFactoryInst* zNPCGoalDutchmanDamage::create(S32 who, RyzMemGrow* grow, void* info) { - owner.reset_lasso_anim(); - owner.get_orbit(); - owner.turn_to_face(owner.flames.splash_loc); //dont know the correct xVec3& - owner.delay = 0; - owner.collis = 0; - return zNPCGoalCommon::Enter(dt, updCtxt); + return new (who, grow) zNPCGoalDutchmanDamage(who, (zNPCDutchman&)*info); } -S32 zNPCGoalDutchmanFlame::Exit(F32 dt, void* updCtxt) +S32 zNPCGoalDutchmanDamage::Enter(F32 dt, void* updCtxt) { - S32 tempR0; - tempR0 = this->owner.flg_vuln; - tempR0 = tempR0 &= 0xFEFFFFFF; - this->owner.flg_vuln = tempR0; + owner.LassoNotify(LASS_EVNT_ENDED); + owner.dissolve(tweak.teleport.fade_time); - owner.stop_flames(); - owner.stop_hand_trail(); - return xGoal::Exit(dt, updCtxt); -} + owner.turn.accel = tweak.teleport.turn_accel; + owner.turn.max_vel = tweak.teleport.turn_max_vel; + owner.move.accel = tweak.teleport.accel; + owner.move.max_vel = tweak.teleport.max_vel; + owner.flag.move = zNPCDutchman::MOVE_FOLLOW; -xFactoryInst* zNPCGoalDutchmanPostFlame::create(S32 who, RyzMemGrow* grow, void* info) -{ - return new (who, grow) zNPCGoalDutchmanPostFlame(who, (zNPCDutchman&)*info); + xVec2 off = owner.turn.dir * tweak.orbit_radius; + + owner.move.dest = owner.get_orbit(); + owner.move.dest.x += off.x; + owner.move.dest.z += off.y; + + return zNPCGoalCommon::Enter(dt, updCtxt); } -S32 zNPCGoalDutchmanPostFlame::Exit(F32 dt, void* updCtxt) +S32 zNPCGoalDutchmanDamage::Exit(F32 dt, void* updCtxt) { - owner.flag.hurting = 0; return xGoal::Exit(dt, updCtxt); } -xFactoryInst* zNPCGoalDutchmanCaught::create(S32 who, RyzMemGrow* grow, void* info) +S32 zNPCGoalDutchmanDamage::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) { - return new (who, grow) zNPCGoalDutchmanCaught(who, (zNPCDutchman&)*info); -} + xAnimState* anim = owner.AnimCurState(); -S32 zNPCGoalDutchmanCaught::Enter(float dt, void* updCtxt) -{ - // TODO - return zNPCGoalCommon::Enter(dt, updCtxt); + if (anim->ID == g_hash_subbanim[ANIM_LassoGrab01]) + { + owner.LassoSyncAnims(LASS_ANIM_GRAB); + } + + if (owner.move.vel.length2() < 0.01f && + (owner.move.dest - owner.get_center()).length2() < 0.01f) + { + *trantype = GOAL_TRAN_SET; + return NPC_GOAL_DUTCHMANREAPPEAR; + } + + return xGoal::Process(trantype, dt, updCtxt, xscn); } -S32 zNPCGoalDutchmanCaught::Exit(F32 dt, void* updCtxt) +xFactoryInst* zNPCGoalDutchmanDeath::create(S32 who, RyzMemGrow* grow, void* info) { - return xGoal::Exit(dt, updCtxt); + return new (who, grow) zNPCGoalDutchmanDeath(who, (zNPCDutchman&)*info); } S32 zNPCGoalDutchmanDeath::Enter(F32 dt, void* updCtxt) @@ -1904,13 +3712,10 @@ S32 zNPCGoalDutchmanDeath::Enter(F32 dt, void* updCtxt) S32 zNPCGoalDutchmanDeath::Exit(F32 dt, void* updCtxt) { - owner.move.dest.assign(dt, 1.0f, 0.0f); - return xGoal::Exit(dt, updCtxt); -} + xVec3& up = (xVec3&)owner.model->Mat[1].up; -xFactoryInst* zNPCGoalDutchmanDeath::create(S32 who, RyzMemGrow* grow, void* info) -{ - return new (who, grow) zNPCGoalDutchmanDeath(who, (zNPCDutchman&)*info); + up.assign(0.0f, 1.0f, 0.0f); + return xGoal::Exit(dt, updCtxt); } S32 zNPCGoalDutchmanDeath::Process(en_trantype* trantype, F32 dt, void* updCtxt, xScene* xscn) diff --git a/src/SB/Game/zNPCTypeDutchman.h b/src/SB/Game/zNPCTypeDutchman.h index d73bc5d38..ffa31bbc2 100644 --- a/src/SB/Game/zNPCTypeDutchman.h +++ b/src/SB/Game/zNPCTypeDutchman.h @@ -122,18 +122,18 @@ struct zNPCDutchman : zNPCSubBoss F32 alpha; //0x2d4 struct { - xVec2 dir; //0x2d4 - F32 vel; //0x2dc - F32 accel; //0x2e0 - F32 max_vel; //0x2e4 + xVec2 dir; //0x2d8 + F32 vel; //0x2e0 + F32 accel; //0x2e4 + F32 max_vel; //0x2e8 } turn; - move_info move; //0x2e8 + move_info move; //0x2ec struct { U8 moreFlags; //0x31c } old; - beam_info beam[2]; //Needed for start_beam func - static_queue waves; //Needed for start_beam func + beam_info beam[2]; //0x320 + static_queue waves; //0x558 struct { F32 size; //0x56c @@ -189,6 +189,8 @@ struct zNPCDutchman : zNPCSubBoss void update_animation(F32); void update_camera(F32); void kill_wave(zNPCDutchman::wave_data&); + void update_wave(zNPCDutchman::wave_data&, F32); + void init_wave(zNPCDutchman::wave_data&, const xVec3&, const xVec3&); void add_splash(const xVec3&, F32); void vanish(); void reappear(); @@ -202,9 +204,9 @@ struct zNPCDutchman : zNPCSubBoss void set_alpha(F32); void start_flames(); void stop_flames(); - void get_eye_loc(S32) const; + xVec3 get_eye_loc(S32) const; U8 check_player_damage(); - void get_hand_loc(S32) const; + xVec3 get_hand_loc(S32) const; void start_hand_trail(); void stop_hand_trail(); void refresh_reticle(); @@ -214,7 +216,11 @@ struct zNPCDutchman : zNPCSubBoss void reset_lasso_anim(); void update_fade(F32); void update_slime(F32); + void add_slime(const xVec3&, F32); + void add_spray(const xVec3&, F32); void reset_speed(); + void halt(F32); + xVec3 get_splash_loc() const; void Damage(en_NPC_DAMAGE_TYPE, xBase*, const xVec3*); U32 AnimPick(S32 rawgoal, en_NPC_GOAL_SPOT gspot, xGoal* goal); void LassoNotify(en_LASSO_EVENT); @@ -222,18 +228,105 @@ struct zNPCDutchman : zNPCSubBoss void update_round(); void decompose(); S32 next_goal(); - S32 goal_delay(); + F32 goal_delay(); void start_eye_glow(); void stop_eye_glow(); void update_eye_glow(F32); - void get_orbit() const; //Weak - xVec3 get_center() const; //Weak + xVec3 random_orbit(const xVec3&, F32, F32) const; void reset_blob_mat(); void render_beam(); void render_halo(); - void enable_emitter(zParEmitter&) const; - void disable_emitter(zParEmitter&) const; + U8 turning() const + { + U8 result = FALSE; + xVec2 facing = { 0.0f, 0.0f }; + + facing.x = model->Mat->at.x; + facing.y = model->Mat->at.z; + + if (!(turn.vel >= -0.00001f && turn.vel <= 0.00001f) || + (!(turn.accel >= -0.00001f && turn.accel <= 0.00001f) && + !(turn.dir.x > turn.dir.y && xabs(turn.dir.x - facing.x) < 0.001f) && + !(turn.dir.x < turn.dir.y && xabs(turn.dir.y - facing.y) < 0.001f))) + { + result = TRUE; + } + + return result; + } + + U8 turning(F32 tol) const + { + U8 result = FALSE; + xVec2 facing = { 0.0f, 0.0f }; + + facing.x = model->Mat->at.x; + facing.y = model->Mat->at.z; + + if (xabs(turn.vel) > tol * turn.max_vel || turn.dir.dot(facing) < 1.0f - tol) + { + result = TRUE; + } + + return result; + } + + const xVec3& get_orbit() const + { + return asset->pos; + } + + const xVec3& get_center() const + { + return *(const xVec3*)&model->Mat->pos; + } + + const xVec3& get_facing() const + { + return *(const xVec3*)&model->Mat->at; + } + + xVec3 get_nose_loc() const + { + return xModelGetBoneLocation(*model, 17); + } + + xVec3 get_chest_loc() const + { + return xModelGetBoneLocation(*model, 2); + } + + void emit_particles(zParEmitter& pe, F32 dt) const + { + xParEmitterEmit(&pe, dt); + } + + void emit_particles(zParEmitter& pe, F32 dt, xParEmitterCustomSettings& info) const + { + xParEmitterEmitCustom(&pe, dt, &info); + } + + void enable_emitter(zParEmitter& pe) const + { + pe.emit_flags |= 1; + } + + void disable_emitter(zParEmitter& pe) const + { + pe.emit_flags &= ~1; + } + + S32 IsAlive() + { + return life > 0; + } + + zNPCLassoInfo* PRIV_GetLassoData() + { + return &lasso_info; + } + U8 PhysicsFlags() const; U8 ColPenByFlags() const; U8 ColChkByFlags() const; @@ -243,6 +336,10 @@ struct zNPCDutchman : zNPCSubBoss struct zNPCGoalDutchmanNil : zNPCGoalCommon { + zNPCGoalDutchmanNil(S32 goalID, zNPCDutchman&) : zNPCGoalCommon(goalID) + { + } + static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -256,6 +353,7 @@ struct zNPCGoalDutchmanInitiate : zNPCGoalCommon S32 Enter(F32, void*); S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -283,7 +381,10 @@ struct zNPCGoalDutchmanDisappear : zNPCGoalCommon { } - S32 Exit(float, void*); + S32 Enter(F32, void*); + S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); + static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -295,7 +396,9 @@ struct zNPCGoalDutchmanTeleport : zNPCGoalCommon { } - S32 Exit(float, void*); + S32 Enter(F32, void*); + S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -308,8 +411,9 @@ struct zNPCGoalDutchmanReappear : zNPCGoalCommon { } - S32 Exit(float, void*); - void reset_speed(); + S32 Enter(F32, void*); + S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -347,7 +451,23 @@ struct zNPCGoalDutchmanBeam : zNPCGoalCommon { } - S32 Exit(float, void*); + S32 Enter(F32, void*); + S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); + void update_stop(F32); + void update_focus(F32); + void update_fire(F32); + void update_unfocus(F32); + void aim_beam(beam_data&, const xVec3&, F32) const; + void calc_beam_loc(xVec2&, F32, const beam_data&) const; + void update_beam(F32, beam_data&, S32); + void refresh_beam(S32); + void start_effects(S32, F32); + void add_miss_effects(S32, F32); + void add_blast_effects(S32, F32); + void add_effects(S32, F32); + void predict_target(xVec3&) const; + static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -370,8 +490,13 @@ struct zNPCGoalDutchmanFlame : zNPCGoalCommon { } - S32 Enter(float, void*); - S32 Exit(float, void*); + S32 Enter(F32, void*); + S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); + void update_wait(F32); + void update_move(F32); + void update_stop(F32); + void refresh_vulnerability(); static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -385,7 +510,9 @@ struct zNPCGoalDutchmanPostFlame : zNPCGoalCommon } static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); + S32 Enter(F32 dt, void* updCtxt); S32 Exit(F32 dt, void* updCtxt); + S32 Process(en_trantype*, F32, void*, xScene*); }; struct zNPCGoalDutchmanCaught : zNPCGoalCommon @@ -397,8 +524,9 @@ struct zNPCGoalDutchmanCaught : zNPCGoalCommon { } - S32 Enter(float, void*); - S32 Exit(float, void*); + S32 Enter(F32, void*); + S32 Exit(F32, void*); + S32 Process(en_trantype*, F32, void*, xScene*); static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); }; @@ -413,12 +541,17 @@ struct zNPCGoalDutchmanDamage : zNPCGoalCommon } static xFactoryInst* create(S32 who, RyzMemGrow* grow, void* info); + S32 Enter(F32 dt, void* updCtxt); S32 Exit(F32 dt, void* updCtxt); + S32 Process(en_trantype*, F32, void*, xScene*); }; +extern zNPCDutchman* dutchman_reticle_ent; +extern F32 dutchman_reticle_radius; + struct delay_goal { - U32 goal; + S32 goal; F32 delay; };