// Copyright (c) ZeniMax Media Inc. // Licensed under the GNU General Public License 2.0. /* xatrix gekk.c */ #include "../g_local.h" #include "m_xatrix_gekk.h" constexpr spawnflags_t SPAWNFLAG_GEKK_CHANT = 8_spawnflag; constexpr spawnflags_t SPAWNFLAG_GEKK_NOJUMPING = 16_spawnflag; constexpr spawnflags_t SPAWNFLAG_GEKK_NOSWIM = 32_spawnflag; static int sound_swing; static int sound_hit; static int sound_hit2; static int sound_speet; static int loogie_hit; static int sound_death; static int sound_pain1; static int sound_sight; static int sound_search; static int sound_step1; static int sound_step2; static int sound_step3; static int sound_thud; static int sound_chantlow; static int sound_chantmid; static int sound_chanthigh; void gekk_swim(edict_t *self); void gekk_jump_takeoff(edict_t *self); void gekk_jump_takeoff2(edict_t *self); void gekk_check_landing(edict_t *self); void gekk_stop_skid(edict_t *self); void water_to_land(edict_t *self); void land_to_water(edict_t *self); void gekk_check_underwater(edict_t *self); void gekk_bite(edict_t *self); void gekk_hit_left(edict_t *self); void gekk_hit_right(edict_t *self); extern const mmove_t gekk_move_attack1; extern const mmove_t gekk_move_attack2; extern const mmove_t gekk_move_chant; extern const mmove_t gekk_move_swim_start; extern const mmove_t gekk_move_swim_loop; extern const mmove_t gekk_move_spit; extern const mmove_t gekk_move_run_start; extern const mmove_t gekk_move_run; bool gekk_check_jump(edict_t *self); // // CHECKATTACK // bool gekk_check_melee(edict_t *self) { if (!self->enemy || self->enemy->health <= 0 || self->monsterinfo.melee_debounce_time > level.time) return false; return range_to(self, self->enemy) <= RANGE_MELEE; } bool gekk_check_jump(edict_t *self) { vec3_t v; float distance; // don't jump if there's no way we can reach standing height if (self->absmin[2] + 125 < self->enemy->absmin[2]) return false; v[0] = self->s.origin[0] - self->enemy->s.origin[0]; v[1] = self->s.origin[1] - self->enemy->s.origin[1]; v[2] = 0; distance = v.length(); if (distance < 100) { return false; } if (distance > 100) { if (frandom() < (self->waterlevel >= WATER_WAIST ? 0.2f : 0.9f)) return false; } return true; } bool gekk_check_jump_close(edict_t *self) { vec3_t v; float distance; v[0] = self->s.origin[0] - self->enemy->s.origin[0]; v[1] = self->s.origin[1] - self->enemy->s.origin[1]; v[2] = 0; distance = v.length(); if (distance < 100) { // don't do this if our head is below their feet if (self->absmax[2] <= self->enemy->absmin[2]) return false; } return true; } MONSTERINFO_CHECKATTACK(gekk_checkattack) (edict_t *self) -> bool { if (!self->enemy || self->enemy->health <= 0) return false; if (gekk_check_melee(self)) { self->monsterinfo.attack_state = AS_MELEE; return true; } if (self->monsterinfo.attack_state == AS_STRAIGHT && self->monsterinfo.attack_finished > level.time) { // keep running fool return false; } if (visible(self, self->enemy, false)) { if (gekk_check_jump(self)) { self->monsterinfo.attack_state = AS_MISSILE; return true; } if (gekk_check_jump_close(self) && !(self->flags & FL_SWIM)) { self->monsterinfo.attack_state = AS_MISSILE; return true; } } return false; } // // SOUNDS // void gekk_step(edict_t *self) { int n = irandom(3); if (n == 0) gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); else if (n == 1) gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); } MONSTERINFO_SIGHT(gekk_sight) (edict_t *self, edict_t *other) -> void { gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); } MONSTERINFO_SEARCH(gekk_search) (edict_t *self) -> void { float r; if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) { r = frandom(); if (r < 0.33f) gi.sound(self, CHAN_VOICE, sound_chantlow, 1, ATTN_NORM, 0); else if (r < 0.66f) gi.sound(self, CHAN_VOICE, sound_chantmid, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_chanthigh, 1, ATTN_NORM, 0); } else gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); self->health += irandom(10, 20); if (self->health > self->max_health) self->health = self->max_health; self->monsterinfo.setskin(self); } MONSTERINFO_SETSKIN(gekk_setskin) (edict_t *self) -> void { if (self->health < (self->max_health / 4)) self->s.skinnum = 2; else if (self->health < (self->max_health / 2)) self->s.skinnum = 1; else self->s.skinnum = 0; } void gekk_swing(edict_t *self) { gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); } void gekk_face(edict_t *self) { M_SetAnimation(self, &gekk_move_run); } // // STAND // void ai_stand_gekk(edict_t *self, float dist) { if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) { ai_move(self, dist); if (!self->spawnflags.has(SPAWNFLAG_MONSTER_AMBUSH) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) { if (self->monsterinfo.idle_time) { self->monsterinfo.idle(self); self->monsterinfo.idle_time = level.time + random_time(15_sec, 30_sec); } else { self->monsterinfo.idle_time = level.time + random_time(15_sec); } } } else ai_stand(self, dist); } mframe_t gekk_frames_stand[] = { { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, // 10 { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, // 20 { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, // 30 { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk, 0, gekk_check_underwater }, }; MMOVE_T(gekk_move_stand) = { FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, nullptr }; mframe_t gekk_frames_standunderwater[] = { { ai_stand_gekk, 14 }, { ai_stand_gekk, 14 }, { ai_stand_gekk, 14 }, { ai_stand_gekk, 14 }, { ai_stand_gekk, 16 }, { ai_stand_gekk, 16 }, { ai_stand_gekk, 16 }, { ai_stand_gekk, 18 }, { ai_stand_gekk, 18 }, { ai_stand_gekk, 18 }, { ai_stand_gekk, 20 }, { ai_stand_gekk, 20 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 24 }, { ai_stand_gekk, 24 }, { ai_stand_gekk, 26 }, { ai_stand_gekk, 26 }, { ai_stand_gekk, 24 }, { ai_stand_gekk, 24 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 22 }, { ai_stand_gekk, 18 }, { ai_stand_gekk, 18 }, { ai_stand_gekk, 18 }, { ai_stand_gekk, 18 } }; MMOVE_T(gekk_move_standunderwater) = { FRAME_swim_01, FRAME_swim_32, gekk_frames_standunderwater, nullptr }; void gekk_swim_loop(edict_t *self) { self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; self->flags |= FL_SWIM; M_SetAnimation(self, &gekk_move_swim_loop); } mframe_t gekk_frames_swim[] = { { ai_run, 14 }, { ai_run, 14 }, { ai_run, 14 }, { ai_run, 14 }, { ai_run, 16 }, { ai_run, 16 }, { ai_run, 16 }, { ai_run, 18 }, { ai_run, 18 }, { ai_run, 18 }, { ai_run, 20 }, { ai_run, 20 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 24 }, { ai_run, 24 }, { ai_run, 26 }, { ai_run, 26 }, { ai_run, 24 }, { ai_run, 24 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 18 }, { ai_run, 18 }, { ai_run, 18 }, { ai_run, 18 } }; MMOVE_T(gekk_move_swim_loop) = { FRAME_swim_01, FRAME_swim_32, gekk_frames_swim, gekk_swim_loop }; mframe_t gekk_frames_swim_start[] = { { ai_run, 14 }, { ai_run, 14 }, { ai_run, 14 }, { ai_run, 14 }, { ai_run, 16 }, { ai_run, 16 }, { ai_run, 16 }, { ai_run, 18 }, { ai_run, 18, gekk_hit_left }, { ai_run, 18 }, { ai_run, 20 }, { ai_run, 20 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 24, gekk_hit_right }, { ai_run, 24 }, { ai_run, 26 }, { ai_run, 26 }, { ai_run, 24 }, { ai_run, 24 }, { ai_run, 22, gekk_bite }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 22 }, { ai_run, 18 }, { ai_run, 18 }, { ai_run, 18 }, { ai_run, 18 } }; MMOVE_T(gekk_move_swim_start) = { FRAME_swim_01, FRAME_swim_32, gekk_frames_swim_start, gekk_swim_loop }; void gekk_swim(edict_t *self) { if (gekk_checkattack(self)) { if (self->enemy->waterlevel < WATER_WAIST && frandom() > 0.7f) water_to_land(self); else M_SetAnimation(self, &gekk_move_swim_start); } else M_SetAnimation(self, &gekk_move_swim_start); } MONSTERINFO_STAND(gekk_stand) (edict_t *self) -> void { if (self->waterlevel >= WATER_WAIST) { self->flags |= FL_SWIM; self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; M_SetAnimation(self, &gekk_move_standunderwater); } else // Don't break out of the chant loop, which is initiated in the spawn function if (self->monsterinfo.active_move != &gekk_move_chant) M_SetAnimation(self, &gekk_move_stand); } void gekk_chant(edict_t *self) { M_SetAnimation(self, &gekk_move_chant); } // // IDLE // void gekk_idle_loop(edict_t *self) { if (frandom() > 0.75f && self->health < self->max_health) self->monsterinfo.nextframe = FRAME_idle_01; } mframe_t gekk_frames_idle[] = { { ai_stand_gekk, 0, gekk_search }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk }, { ai_stand_gekk, 0, gekk_idle_loop } }; MMOVE_T(gekk_move_idle) = { FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_stand }; MMOVE_T(gekk_move_idle2) = { FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_face }; mframe_t gekk_frames_idle2[] = { { ai_move, 0, gekk_search }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, gekk_idle_loop } }; MMOVE_T(gekk_move_chant) = { FRAME_idle_01, FRAME_idle_32, gekk_frames_idle2, gekk_chant }; MONSTERINFO_IDLE(gekk_idle) (edict_t *self) -> void { if (self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) || self->waterlevel < WATER_WAIST) M_SetAnimation(self, &gekk_move_idle); else M_SetAnimation(self, &gekk_move_swim_start); // gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); } // // WALK // mframe_t gekk_frames_walk[] = { { ai_walk, 3.849f, gekk_check_underwater }, // frame 0 { ai_walk, 19.606f }, // frame 1 { ai_walk, 25.583f }, // frame 2 { ai_walk, 34.625f, gekk_step }, // frame 3 { ai_walk, 27.365f }, // frame 4 { ai_walk, 28.480f }, // frame 5 }; MMOVE_T(gekk_move_walk) = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, nullptr }; MONSTERINFO_WALK(gekk_walk) (edict_t *self) -> void { M_SetAnimation(self, &gekk_move_walk); } // // RUN // MONSTERINFO_RUN(gekk_run_start) (edict_t *self) -> void { if (!self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) && self->waterlevel >= WATER_WAIST) { M_SetAnimation(self, &gekk_move_swim_start); } else { M_SetAnimation(self, &gekk_move_run_start); } } void gekk_run(edict_t *self) { if (!self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) && self->waterlevel >= WATER_WAIST) { M_SetAnimation(self, &gekk_move_swim_start); return; } else { if (self->monsterinfo.aiflags & AI_STAND_GROUND) M_SetAnimation(self, &gekk_move_stand); else M_SetAnimation(self, &gekk_move_run); } } mframe_t gekk_frames_run[] = { { ai_run, 3.849f, gekk_check_underwater }, // frame 0 { ai_run, 19.606f }, // frame 1 { ai_run, 25.583f }, // frame 2 { ai_run, 34.625f, gekk_step }, // frame 3 { ai_run, 27.365f }, // frame 4 { ai_run, 28.480f }, // frame 5 }; MMOVE_T(gekk_move_run) = { FRAME_run_01, FRAME_run_06, gekk_frames_run, nullptr }; mframe_t gekk_frames_run_st[] = { { ai_run, 0.212f }, // frame 0 { ai_run, 19.753f }, // frame 1 }; MMOVE_T(gekk_move_run_start) = { FRAME_stand_01, FRAME_stand_02, gekk_frames_run_st, gekk_run }; // // MELEE // void gekk_hit_left(edict_t *self) { if (!self->enemy) return; vec3_t aim = { MELEE_DISTANCE, self->mins[0], 8 }; if (fire_hit(self, aim, irandom(5, 10), 100)) gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); else { gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; } } void gekk_hit_right(edict_t *self) { if (!self->enemy) return; vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 8 }; if (fire_hit(self, aim, irandom(5, 10), 100)) gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); else { gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; } } void gekk_check_refire(edict_t *self) { if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) return; if (range_to(self, self->enemy) <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time) { if (self->s.frame == FRAME_clawatk3_09) M_SetAnimation(self, &gekk_move_attack2); else if (self->s.frame == FRAME_clawatk5_09) M_SetAnimation(self, &gekk_move_attack1); } } TOUCH(loogie_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void { if (other == self->owner) return; if (tr.surface && (tr.surface->flags & SURF_SKY)) { G_FreeEdict(self); return; } if (self->owner->client) PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); if (other->takedamage) T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, MOD_GEKK); gi.sound(self, CHAN_AUTO, loogie_hit, 1.0f, ATTN_NORM, 0); G_FreeEdict(self); }; void fire_loogie(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed) { edict_t *loogie; trace_t tr; loogie = G_Spawn(); loogie->s.origin = start; loogie->s.old_origin = start; loogie->s.angles = vectoangles(dir); loogie->velocity = dir * speed; loogie->movetype = MOVETYPE_FLYMISSILE; loogie->clipmask = MASK_PROJECTILE; loogie->solid = SOLID_BBOX; // Paril: this was originally the wrong effect, // but it makes it look more acid-y. loogie->s.effects |= EF_BLASTER; loogie->s.renderfx |= RF_FULLBRIGHT; loogie->s.modelindex = gi.modelindex("models/objects/loogy/tris.md2"); loogie->owner = self; loogie->touch = loogie_touch; loogie->nextthink = level.time + 2_sec; loogie->think = G_FreeEdict; loogie->dmg = damage; loogie->svflags |= SVF_PROJECTILE; gi.linkentity(loogie); tr = gi.traceline(self->s.origin, loogie->s.origin, loogie, MASK_PROJECTILE); if (tr.fraction < 1.0f) { loogie->s.origin = tr.endpos + (tr.plane.normal * 1.f); loogie->touch(loogie, tr.ent, tr, false); } } void loogie(edict_t *self) { vec3_t start; vec3_t forward, right, up; vec3_t end; vec3_t dir; vec3_t gekkoffset = { -18, -0.8f, 24 }; if (!self->enemy || self->enemy->health <= 0) return; AngleVectors(self->s.angles, forward, right, up); start = M_ProjectFlashSource(self, gekkoffset, forward, right); start += (up * 2); end = self->enemy->s.origin; end[2] += self->enemy->viewheight; dir = end - start; dir.normalize(); fire_loogie(self, start, dir, 5, 550); gi.sound(self, CHAN_BODY, sound_speet, 1.0f, ATTN_NORM, 0); } void reloogie(edict_t *self) { if (frandom() > 0.8f && self->health < self->max_health) { M_SetAnimation(self, &gekk_move_idle2); return; } if (self->enemy->health >= 0) if (frandom() > 0.7f && (range_to(self, self->enemy) <= RANGE_NEAR)) M_SetAnimation(self, &gekk_move_spit); } mframe_t gekk_frames_spit[] = { { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge, 0, loogie }, { ai_charge, 0, reloogie } }; MMOVE_T(gekk_move_spit) = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run_start }; mframe_t gekk_frames_attack1[] = { { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge, 0, gekk_hit_left }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge, 0, gekk_check_refire } }; MMOVE_T(gekk_move_attack1) = { FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run_start }; mframe_t gekk_frames_attack2[] = { { ai_charge }, { ai_charge }, { ai_charge, 0, gekk_hit_left }, { ai_charge }, { ai_charge }, { ai_charge, 0, gekk_hit_right }, { ai_charge }, { ai_charge }, { ai_charge, 0, gekk_check_refire } }; MMOVE_T(gekk_move_attack2) = { FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run_start }; void gekk_check_underwater(edict_t *self) { if (!self->spawnflags.has(SPAWNFLAG_GEKK_NOSWIM) && self->waterlevel >= WATER_WAIST) land_to_water(self); } mframe_t gekk_frames_leapatk[] = { { ai_charge }, // frame 0 { ai_charge, -0.387f }, // frame 1 { ai_charge, -1.113f }, // frame 2 { ai_charge, -0.237f }, // frame 3 { ai_charge, 6.720f, gekk_jump_takeoff }, // frame 4 last frame on ground { ai_charge, 6.414f }, // frame 5 leaves ground { ai_charge, 0.163f }, // frame 6 { ai_charge, 28.316f }, // frame 7 { ai_charge, 24.198f }, // frame 8 { ai_charge, 31.742f }, // frame 9 { ai_charge, 35.977f, gekk_check_landing }, // frame 10 last frame in air { ai_charge, 12.303f, gekk_stop_skid }, // frame 11 feet back on ground { ai_charge, 20.122f, gekk_stop_skid }, // frame 12 { ai_charge, -1.042f, gekk_stop_skid }, // frame 13 { ai_charge, 2.556f, gekk_stop_skid }, // frame 14 { ai_charge, 0.544f, gekk_stop_skid }, // frame 15 { ai_charge, 1.862f, gekk_stop_skid }, // frame 16 { ai_charge, 1.224f, gekk_stop_skid }, // frame 17 { ai_charge, -0.457f, gekk_check_underwater }, // frame 18 }; MMOVE_T(gekk_move_leapatk) = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run_start }; mframe_t gekk_frames_leapatk2[] = { { ai_charge }, // frame 0 { ai_charge, -0.387f }, // frame 1 { ai_charge, -1.113f }, // frame 2 { ai_charge, -0.237f }, // frame 3 { ai_charge, 6.720f, gekk_jump_takeoff2 }, // frame 4 last frame on ground { ai_charge, 6.414f }, // frame 5 leaves ground { ai_charge, 0.163f }, // frame 6 { ai_charge, 28.316f }, // frame 7 { ai_charge, 24.198f }, // frame 8 { ai_charge, 31.742f }, // frame 9 { ai_charge, 35.977f, gekk_check_landing }, // frame 10 last frame in air { ai_charge, 12.303f, gekk_stop_skid }, // frame 11 feet back on ground { ai_charge, 20.122f, gekk_stop_skid }, // frame 12 { ai_charge, -1.042f, gekk_stop_skid }, // frame 13 { ai_charge, 2.556f, gekk_stop_skid }, // frame 14 { ai_charge, 0.544f, gekk_stop_skid }, // frame 15 { ai_charge, 1.862f, gekk_stop_skid }, // frame 16 { ai_charge, 1.224f, gekk_stop_skid }, // frame 17 { ai_charge, -0.457f, gekk_check_underwater }, // frame 18 }; MMOVE_T(gekk_move_leapatk2) = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run_start }; void gekk_bite(edict_t *self) { if (!self->enemy) return; vec3_t aim = { MELEE_DISTANCE, 0, 0 }; fire_hit(self, aim, 5, 0); } void gekk_preattack(edict_t *self) { // underwater attack sound // gi.sound (self, CHAN_WEAPON, something something underwater sound, 1, ATTN_NORM, 0); return; } mframe_t gekk_frames_attack[] = { { ai_charge, 16, gekk_preattack }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16, gekk_bite }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16, gekk_bite }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16, gekk_hit_left }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16 }, { ai_charge, 16, gekk_hit_right }, { ai_charge, 16 }, { ai_charge, 16 } }; MMOVE_T(gekk_move_attack) = { FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run_start }; MONSTERINFO_MELEE(gekk_melee) (edict_t *self) -> void { if (self->waterlevel >= WATER_WAIST) { M_SetAnimation(self, &gekk_move_attack); } else { float r = frandom(); if (r > 0.66f) M_SetAnimation(self, &gekk_move_attack1); else M_SetAnimation(self, &gekk_move_attack2); } } // // ATTACK // TOUCH(gekk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void { if (self->health <= 0) { self->touch = nullptr; return; } if (self->style == 1 && other->takedamage) { if (self->velocity.length() > 200) { vec3_t point; vec3_t normal; int damage; normal = self->velocity; normal.normalize(); point = self->s.origin + (normal * self->maxs[0]); damage = irandom(10, 20); T_Damage(other, self, self, self->velocity, point, normal, damage, damage, DAMAGE_NONE, MOD_GEKK); self->style = 0; } } if (!M_CheckBottom(self)) { if (self->groundentity) { self->monsterinfo.nextframe = FRAME_leapatk_11; self->touch = nullptr; } return; } self->touch = nullptr; } void gekk_jump_takeoff(edict_t *self) { vec3_t forward; gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); AngleVectors(self->s.angles, forward, nullptr, nullptr); self->s.origin[2] += 1; // high jump if (gekk_check_jump(self)) { self->velocity = forward * 700; self->velocity[2] = 250; } else { self->velocity = forward * 250; self->velocity[2] = 400; } self->groundentity = nullptr; self->monsterinfo.aiflags |= AI_DUCKED; self->monsterinfo.attack_finished = level.time + 3_sec; self->touch = gekk_jump_touch; self->style = 1; } void gekk_jump_takeoff2(edict_t *self) { vec3_t forward; gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); AngleVectors(self->s.angles, forward, nullptr, nullptr); self->s.origin[2] = self->enemy->s.origin[2]; if (gekk_check_jump(self)) { self->velocity = forward * 300; self->velocity[2] = 250; } else { self->velocity = forward * 150; self->velocity[2] = 300; } self->groundentity = nullptr; self->monsterinfo.aiflags |= AI_DUCKED; self->monsterinfo.attack_finished = level.time + 3_sec; self->touch = gekk_jump_touch; self->style = 1; } void gekk_stop_skid(edict_t *self) { if (self->groundentity) self->velocity = {}; } void gekk_check_landing(edict_t *self) { if (self->groundentity) { gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); self->monsterinfo.attack_finished = 0_ms; if (self->monsterinfo.unduck) self->monsterinfo.unduck(self); self->velocity = {}; return; } // Paril: allow them to "pull" up ledges vec3_t fwd; AngleVectors(self->s.angles, fwd, nullptr, nullptr); if (fwd.dot(self->velocity) < 200) self->velocity += (fwd * 200.f); // note to self // causing skid if (level.time > self->monsterinfo.attack_finished) self->monsterinfo.nextframe = FRAME_leapatk_11; else { self->monsterinfo.nextframe = FRAME_leapatk_12; } } MONSTERINFO_ATTACK(gekk_attack) (edict_t *self) -> void { float r = range_to(self, self->enemy); if (self->flags & FL_SWIM) { if (self->enemy && self->enemy->waterlevel >= WATER_WAIST && r <= RANGE_NEAR) return; self->flags &= ~FL_SWIM; self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; M_SetAnimation(self, &gekk_move_leapatk); self->monsterinfo.nextframe = FRAME_leapatk_05; } else { if (r >= RANGE_MID) { if (frandom() > 0.5f) { M_SetAnimation(self, &gekk_move_spit); } else { M_SetAnimation(self, &gekk_move_run_start); self->monsterinfo.attack_finished = level.time + 2_sec; } } else if (frandom() > 0.7f) { M_SetAnimation(self, &gekk_move_spit); } else { if (self->spawnflags.has(SPAWNFLAG_GEKK_NOJUMPING) || frandom() > 0.7f) { M_SetAnimation(self, &gekk_move_run_start); self->monsterinfo.attack_finished = level.time + 1.4_sec; } else { M_SetAnimation(self, &gekk_move_leapatk); } } } } // // PAIN // mframe_t gekk_frames_pain[] = { { ai_move }, // frame 0 { ai_move }, // frame 1 { ai_move }, // frame 2 { ai_move }, // frame 3 { ai_move }, // frame 4 { ai_move }, // frame 5 }; MMOVE_T(gekk_move_pain) = { FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run_start }; mframe_t gekk_frames_pain1[] = { { ai_move }, // frame 0 { ai_move }, // frame 1 { ai_move }, // frame 2 { ai_move }, // frame 3 { ai_move }, // frame 4 { ai_move }, // frame 5 { ai_move }, // frame 6 { ai_move }, // frame 7 { ai_move }, // frame 8 { ai_move }, // frame 9 { ai_move, 0, gekk_check_underwater } }; MMOVE_T(gekk_move_pain1) = { FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run_start }; mframe_t gekk_frames_pain2[] = { { ai_move }, // frame 0 { ai_move }, // frame 1 { ai_move }, // frame 2 { ai_move }, // frame 3 { ai_move }, // frame 4 { ai_move }, // frame 5 { ai_move }, // frame 6 { ai_move }, // frame 7 { ai_move }, // frame 8 { ai_move }, // frame 9 { ai_move }, // frame 10 { ai_move }, // frame 11 { ai_move, 0, gekk_check_underwater }, }; MMOVE_T(gekk_move_pain2) = { FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run_start }; PAIN(gekk_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void { float r; if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) { self->spawnflags &= ~SPAWNFLAG_GEKK_CHANT; return; } if (level.time < self->pain_debounce_time) return; self->pain_debounce_time = level.time + 3_sec; gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); if (self->waterlevel >= WATER_WAIST) { if (!(self->flags & FL_SWIM)) { self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; self->flags |= FL_SWIM; } if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare M_SetAnimation(self, &gekk_move_pain); } else if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare { r = frandom(); if (r > 0.5f) M_SetAnimation(self, &gekk_move_pain1); else M_SetAnimation(self, &gekk_move_pain2); } } // // DEATH // void gekk_dead(edict_t *self) { self->mins = { -16, -16, -24 }; self->maxs = { 16, 16, -8 }; monster_dead(self); } void gekk_gib(edict_t *self, int damage) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); ThrowGibs(self, damage, { { "models/objects/gekkgib/pelvis/tris.md2", GIB_ACID }, { 2, "models/objects/gekkgib/arm/tris.md2", GIB_ACID }, { "models/objects/gekkgib/torso/tris.md2", GIB_ACID }, { "models/objects/gekkgib/claw/tris.md2", GIB_ACID }, { 2, "models/objects/gekkgib/leg/tris.md2", GIB_ACID }, { "models/objects/gekkgib/head/tris.md2", GIB_ACID | GIB_HEAD } }); } void gekk_gibfest(edict_t *self) { gekk_gib(self, 20); self->deadflag = true; } void isgibfest(edict_t *self) { if (frandom() > 0.9f) gekk_gibfest(self); } static void gekk_shrink(edict_t *self) { self->maxs[2] = 0; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); } mframe_t gekk_frames_death1[] = { { ai_move, -5.151f }, // frame 0 { ai_move, -12.223f }, // frame 1 { ai_move, -11.484f }, // frame 2 { ai_move, -17.952f }, // frame 3 { ai_move, -6.953f }, // frame 4 { ai_move, -7.393f, gekk_shrink }, // frame 5 { ai_move, -10.713f }, // frame 6 { ai_move, -17.464f }, // frame 7 { ai_move, -11.678f }, // frame 8 { ai_move, -11.678f } // frame 9 }; MMOVE_T(gekk_move_death1) = { FRAME_death1_01, FRAME_death1_10, gekk_frames_death1, gekk_dead }; mframe_t gekk_frames_death3[] = { { ai_move }, // frame 0 { ai_move, 0.022f }, // frame 1 { ai_move, 0.169f }, // frame 2 { ai_move, -0.710f }, // frame 3 { ai_move, -13.446f }, // frame 4 { ai_move, -7.654f, isgibfest }, // frame 5 { ai_move, -31.951f }, // frame 6 }; MMOVE_T(gekk_move_death3) = { FRAME_death3_01, FRAME_death3_07, gekk_frames_death3, gekk_dead }; mframe_t gekk_frames_death4[] = { { ai_move, 5.103f }, // frame 0 { ai_move, -4.808f }, // frame 1 { ai_move, -10.509f }, // frame 2 { ai_move, -9.899f }, // frame 3 { ai_move, 4.033f, isgibfest }, // frame 4 { ai_move, -5.197f }, // frame 5 { ai_move, -0.919f }, // frame 6 { ai_move, -8.821f }, // frame 7 { ai_move, -5.626f }, // frame 8 { ai_move, -8.865f, isgibfest }, // frame 9 { ai_move, -0.845f }, // frame 10 { ai_move, 1.986f }, // frame 11 { ai_move, 0.170f }, // frame 12 { ai_move, 1.339f, isgibfest }, // frame 13 { ai_move, -0.922f }, // frame 14 { ai_move, 0.818f }, // frame 15 { ai_move, -1.288f }, // frame 16 { ai_move, -1.408f, isgibfest }, // frame 17 { ai_move, -7.787f }, // frame 18 { ai_move, -3.995f }, // frame 19 { ai_move, -4.604f }, // frame 20 { ai_move, -1.715f, isgibfest }, // frame 21 { ai_move, -0.564f }, // frame 22 { ai_move, -0.597f }, // frame 23 { ai_move, 0.074f }, // frame 24 { ai_move, -0.309f, isgibfest }, // frame 25 { ai_move, -0.395f }, // frame 26 { ai_move, -0.501f }, // frame 27 { ai_move, -0.325f }, // frame 28 { ai_move, -0.931f, isgibfest }, // frame 29 { ai_move, -1.433f }, // frame 30 { ai_move, -1.626f }, // frame 31 { ai_move, 4.680f }, // frame 32 { ai_move, 0.560f }, // frame 33 { ai_move, -0.549f, gekk_gibfest } // frame 34 }; MMOVE_T(gekk_move_death4) = { FRAME_death4_01, FRAME_death4_35, gekk_frames_death4, gekk_dead }; mframe_t gekk_frames_wdeath[] = { { ai_move }, // frame 0 { ai_move }, // frame 1 { ai_move }, // frame 2 { ai_move }, // frame 3 { ai_move }, // frame 4 { ai_move }, // frame 5 { ai_move }, // frame 6 { ai_move }, // frame 7 { ai_move }, // frame 8 { ai_move }, // frame 9 { ai_move }, // frame 10 { ai_move }, // frame 11 { ai_move }, // frame 12 { ai_move }, // frame 13 { ai_move }, // frame 14 { ai_move }, // frame 15 { ai_move }, // frame 16 { ai_move }, // frame 17 { ai_move }, // frame 18 { ai_move }, // frame 19 { ai_move }, // frame 20 { ai_move }, // frame 21 { ai_move }, // frame 22 { ai_move }, // frame 23 { ai_move }, // frame 24 { ai_move }, // frame 25 { ai_move }, // frame 26 { ai_move }, // frame 27 { ai_move }, // frame 28 { ai_move }, // frame 29 { ai_move }, // frame 30 { ai_move }, // frame 31 { ai_move }, // frame 32 { ai_move }, // frame 33 { ai_move }, // frame 34 { ai_move }, // frame 35 { ai_move }, // frame 36 { ai_move }, // frame 37 { ai_move }, // frame 38 { ai_move }, // frame 39 { ai_move }, // frame 40 { ai_move }, // frame 41 { ai_move }, // frame 42 { ai_move }, // frame 43 { ai_move } // frame 44 }; MMOVE_T(gekk_move_wdeath) = { FRAME_wdeath_01, FRAME_wdeath_45, gekk_frames_wdeath, gekk_dead }; DIE(gekk_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { float r; if (M_CheckGib(self, mod)) { gekk_gib(self, damage); self->deadflag = true; return; } if (self->deadflag) return; gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = true; self->takedamage = true; if (self->waterlevel >= WATER_WAIST) { gekk_shrink(self); M_SetAnimation(self, &gekk_move_wdeath); } else { r = frandom(); if (r > 0.66f) M_SetAnimation(self, &gekk_move_death1); else if (r > 0.33f) M_SetAnimation(self, &gekk_move_death3); else M_SetAnimation(self, &gekk_move_death4); } } /* duck */ mframe_t gekk_frames_lduck[] = { { ai_move }, // frame 0 { ai_move }, // frame 1 { ai_move }, // frame 2 { ai_move }, // frame 3 { ai_move }, // frame 4 { ai_move }, // frame 5 { ai_move }, // frame 6 { ai_move }, // frame 7 { ai_move }, // frame 8 { ai_move }, // frame 9 { ai_move }, // frame 10 { ai_move }, // frame 11 { ai_move } // frame 12 }; MMOVE_T(gekk_move_lduck) = { FRAME_lduck_01, FRAME_lduck_13, gekk_frames_lduck, gekk_run_start }; mframe_t gekk_frames_rduck[] = { { ai_move }, // frame 0 { ai_move }, // frame 1 { ai_move }, // frame 2 { ai_move }, // frame 3 { ai_move }, // frame 4 { ai_move }, // frame 5 { ai_move }, // frame 6 { ai_move }, // frame 7 { ai_move }, // frame 8 { ai_move }, // frame 9 { ai_move }, // frame 10 { ai_move }, // frame 11 { ai_move } // frame 12 }; MMOVE_T(gekk_move_rduck) = { FRAME_rduck_01, FRAME_rduck_13, gekk_frames_rduck, gekk_run_start }; MONSTERINFO_DODGE(gekk_dodge) (edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) -> void { // [Paril-KEX] this dodge is bad #if 0 float r; r = frandom(); if (r > 0.25f) return; if (!self->enemy) self->enemy = attacker; if (self->waterlevel) { M_SetAnimation(self, &gekk_move_attack); return; } if (skill->integer == 0) { r = frandom(); if (r > 0.5f) M_SetAnimation(self, &gekk_move_lduck); else M_SetAnimation(self, &gekk_move_rduck); return; } self->monsterinfo.pausetime = level.time + eta + 300_ms; r = frandom(); if (skill->integer == 1) { if (r > 0.33f) { r = frandom(); if (r > 0.5f) M_SetAnimation(self, &gekk_move_lduck); else M_SetAnimation(self, &gekk_move_rduck); } else { r = frandom(); if (r > 0.66f) M_SetAnimation(self, &gekk_move_attack1); else M_SetAnimation(self, &gekk_move_attack2); } return; } if (skill->integer == 2) { if (r > 0.66f) { r = frandom(); if (r > 0.5f) M_SetAnimation(self, &gekk_move_lduck); else M_SetAnimation(self, &gekk_move_rduck); } else { r = frandom(); if (r > 0.66f) M_SetAnimation(self, &gekk_move_attack1); else M_SetAnimation(self, &gekk_move_attack2); } return; } r = frandom(); if (r > 0.66f) M_SetAnimation(self, &gekk_move_attack1); else M_SetAnimation(self, &gekk_move_attack2); #endif } // // SPAWN // static void gekk_set_fly_parameters(edict_t *self) { self->monsterinfo.fly_thrusters = false; self->monsterinfo.fly_acceleration = 25.f; self->monsterinfo.fly_speed = 150.f; // only melee, so get in close self->monsterinfo.fly_min_distance = 10.f; self->monsterinfo.fly_max_distance = 10.f; } //================ // ROGUE void gekk_jump_down(edict_t *self) { vec3_t forward, up; AngleVectors(self->s.angles, forward, nullptr, up); self->velocity += (forward * 100); self->velocity += (up * 300); } void gekk_jump_up(edict_t *self) { vec3_t forward, up; AngleVectors(self->s.angles, forward, nullptr, up); self->velocity += (forward * 200); self->velocity += (up * 450); } void gekk_jump_wait_land(edict_t *self) { if (!monster_jump_finished(self) && self->groundentity == nullptr) self->monsterinfo.nextframe = self->s.frame; else self->monsterinfo.nextframe = self->s.frame + 1; } mframe_t gekk_frames_jump_up[] = { { ai_move, -8, gekk_jump_up }, { ai_move, -8 }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, gekk_jump_wait_land }, { ai_move } }; MMOVE_T(gekk_move_jump_up) = { FRAME_leapatk_04, FRAME_leapatk_11, gekk_frames_jump_up, gekk_run }; mframe_t gekk_frames_jump_down[] = { { ai_move, 0, gekk_jump_down }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, gekk_jump_wait_land }, { ai_move } }; MMOVE_T(gekk_move_jump_down) = { FRAME_leapatk_04, FRAME_leapatk_11, gekk_frames_jump_down, gekk_run }; void gekk_jump_updown(edict_t *self, blocked_jump_result_t result) { if (!self->enemy) return; if (result == blocked_jump_result_t::JUMP_JUMP_UP) M_SetAnimation(self, &gekk_move_jump_up); else M_SetAnimation(self, &gekk_move_jump_down); } /* === Blocked === */ MONSTERINFO_BLOCKED(gekk_blocked) (edict_t *self, float dist) -> bool { if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) { if (result != blocked_jump_result_t::JUMP_TURN) gekk_jump_updown(self, result); return true; } if (blocked_checkplat(self, dist)) return true; return false; } // ROGUE //================ /*QUAKED monster_gekk (1 .5 0) (-16 -16 -24) (16 16 24) Ambush Trigger_Spawn Sight Chant NoJumping */ void SP_monster_gekk(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } sound_swing = gi.soundindex("gek/gk_atck1.wav"); sound_hit = gi.soundindex("gek/gk_atck2.wav"); sound_hit2 = gi.soundindex("gek/gk_atck3.wav"); sound_speet = gi.soundindex("gek/gk_atck4.wav"); loogie_hit = gi.soundindex("gek/loogie_hit.wav"); sound_death = gi.soundindex("gek/gk_deth1.wav"); sound_pain1 = gi.soundindex("gek/gk_pain1.wav"); sound_sight = gi.soundindex("gek/gk_sght1.wav"); sound_search = gi.soundindex("gek/gk_idle1.wav"); sound_step1 = gi.soundindex("gek/gk_step1.wav"); sound_step2 = gi.soundindex("gek/gk_step2.wav"); sound_step3 = gi.soundindex("gek/gk_step3.wav"); sound_thud = gi.soundindex("mutant/thud1.wav"); sound_chantlow = gi.soundindex("gek/gek_low.wav"); sound_chantmid = gi.soundindex("gek/gek_mid.wav"); sound_chanthigh = gi.soundindex("gek/gek_high.wav"); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->s.modelindex = gi.modelindex("models/monsters/gekk/tris.md2"); self->mins = { -18, -18, -24 }; self->maxs = { 18, 18, 24 }; gi.modelindex("models/objects/gekkgib/pelvis/tris.md2"); gi.modelindex("models/objects/gekkgib/arm/tris.md2"); gi.modelindex("models/objects/gekkgib/torso/tris.md2"); gi.modelindex("models/objects/gekkgib/claw/tris.md2"); gi.modelindex("models/objects/gekkgib/leg/tris.md2"); gi.modelindex("models/objects/gekkgib/head/tris.md2"); self->health = 125 * st.health_multiplier; self->gib_health = -30; self->mass = 300; self->pain = gekk_pain; self->die = gekk_die; self->monsterinfo.stand = gekk_stand; self->monsterinfo.walk = gekk_walk; self->monsterinfo.run = gekk_run_start; self->monsterinfo.dodge = gekk_dodge; self->monsterinfo.attack = gekk_attack; self->monsterinfo.melee = gekk_melee; self->monsterinfo.sight = gekk_sight; self->monsterinfo.search = gekk_search; self->monsterinfo.idle = gekk_idle; self->monsterinfo.checkattack = gekk_checkattack; self->monsterinfo.setskin = gekk_setskin; gi.linkentity(self); M_SetAnimation(self, &gekk_move_stand); self->monsterinfo.scale = MODEL_SCALE; walkmonster_start(self); if (self->spawnflags.has(SPAWNFLAG_GEKK_CHANT)) M_SetAnimation(self, &gekk_move_chant); self->monsterinfo.can_jump = !(self->spawnflags & SPAWNFLAG_GEKK_NOJUMPING); self->monsterinfo.drop_height = 256; self->monsterinfo.jump_height = 68; self->monsterinfo.blocked = gekk_blocked; gekk_set_fly_parameters(self); } void water_to_land(edict_t *self) { self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; self->flags &= ~FL_SWIM; self->yaw_speed = 20; self->viewheight = 25; M_SetAnimation(self, &gekk_move_leapatk2); self->mins = { -18, -18, -24 }; self->maxs = { 18, 18, 24 }; } void land_to_water(edict_t *self) { self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; self->flags |= FL_SWIM; self->yaw_speed = 10; self->viewheight = 10; M_SetAnimation(self, &gekk_move_swim_start); self->mins = { -18, -18, -24 }; self->maxs = { 18, 18, 16 }; }