alias selectteam "qc_cmd_cmd selectteam ${* ?}" // Attempt to choose a team to join into
alias selfstuff "qc_cmd_cmd selfstuff ${* ?}" // Stuffcmd a command to your own client
alias sentcvar "qc_cmd_cmd sentcvar ${* ?}" // New system for sending a client cvar to the server
+alias spawnmob "qc_cmd_cmd mobspawn ${* ?}" // Spawn a monster infront of the player
alias spectate "qc_cmd_cmd spectate ${* ?}" // Become an observer
alias suggestmap "qc_cmd_cmd suggestmap ${* ?}" // Suggest a map to the mapvote at match end
//alias tell "qc_cmd_cmd tell ${* ?}" // Send a message directly to a player
// mutator aliases
alias sandbox "cmd g_sandbox ${* ?}"
+alias spawnturret "cmd turretspawn ${* ?}"
+alias debugmonsters "cmd debugmonsters ${* ?}"
+alias upgradeturret "cmd buffturret ${* ?}"
+alias rmturret "cmd turretremove ${* ?}"
// ============================================================
alias anticheat "qc_cmd_sv anticheat ${* ?}" // Create an anticheat report for a client
alias bbox "qc_cmd_sv bbox ${* ?}" // Print detailed information about world size
alias bot_cmd "qc_cmd_sv bot_cmd ${* ?}" // Control and send commands to bots
+alias butcher "qc_cmd_sv butcher ${* ?}" // Remove all monsters on the map
alias cointoss "qc_cmd_sv cointoss ${* ?}" // Flip a virtual coin and give random result
alias database "qc_cmd_sv database ${* ?}" // Extra controls of the serverprogs database
alias defer_clear "qc_cmd_sv defer_clear ${* ?}" // Clear all queued defer commands for a specific client
exec vehicles.cfg
exec crosshairs.cfg
exec gamemodes.cfg
+exec monsters.cfg
+exec za.cfg
// load console command aliases and settings
exec commands.cfg
alias cl_hook_gamestart_cts
alias cl_hook_gamestart_ka
alias cl_hook_gamestart_ft
+alias cl_hook_gamestart_td
alias cl_hook_gameend
alias cl_hook_activeweapon
alias sv_hook_gamestart_cts
alias sv_hook_gamestart_ka
alias sv_hook_gamestart_ft
+alias sv_hook_gamestart_td
alias sv_hook_gamerestart
alias sv_hook_gameend
set g_ft_respawn_waves 0
set g_ft_respawn_delay 0
set g_ft_weapon_stay 0
+set g_td_respawn_waves 0
+set g_td_respawn_delay 0
+set g_td_weapon_stay 0
// =======
set g_balance_curse_slow_highspeed 0.6
set g_balance_rune_speed_combo_highspeed 0.9
+// ===============
+// tower defense
+// ===============
+set g_td 0 "Tower Defense: protect the generator/s from waves of monsters"
+set g_td_force_settings 0 "if enabled, don't use map settings (monster count, start wave etc.)"
+set g_td_start_wave 1
+set g_td_generator_health 700
+set g_td_generator_damaged_points 20 "player loses this many points if the generator was damaged during the wave"
+set g_td_current_monsters 10 "maximum monsters that can be spawned simultaneously"
+set g_td_monster_count 10
+set g_td_monster_count_increment 5
+set g_td_buildphase_time 20
+set g_td_generator_dontend 0 "don't change maps when a generator is destroyed (only if there is more than 1 generator)"
+set g_td_pvp 0
+set g_td_monsters_skill_start 1 "set to 0 to use g_monsters_skill instead"
+set g_td_monsters_skill_increment 0.1
+set g_td_monsters_spawnshield_time 2
+set g_td_max_waves 8
+set g_td_kill_points 5
+set g_td_turretkill_points 3
+set g_td_turret_max 4
+set g_td_turret_plasma_cost 50
+set g_td_turret_mlrs_cost 80
+set g_td_turret_walker_cost 100
+set g_td_tower_buff_cost 70
+set g_td_turret_upgrade_cost 100
--- /dev/null
+1 12 10 1 // demon stand\r14 7 10 1 // demon walk\r22 5 10 1 // demon run\r28 11 10 0 // demon leap\r40 5 10 0 // demon pain\r46 8 10 0 // demon death\r55 14 10 1 // demon melee
\ No newline at end of file
--- /dev/null
+Fiendish Deamon Doll model
+
+Copyright (C) 2003 Ulrich Gablraith
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+History:
+
+7-5-04
+
+The model was given to Mapes so he could port it into OpenQuartz by permission.
+
+Greg Mapes
\ No newline at end of file
--- /dev/null
+/*
+Generated framegroups file for dog
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 25 60 1 // dog idle
+26 37 60 1 // dog walk
+63 37 120 1 // dog run
+100 50 60 1 // dog attack
+150 42 60 0 // dog die
+192 25 60 1 // dog pain
--- /dev/null
+This cerberus model is GPL 2.0 compatible (http://opengameart.org/content/animated-cerberus).
\ No newline at end of file
--- /dev/null
+1 6 10 1 // enforcer stand\r8 15 10 1 // enforcer walk\r24 7 10 1 // enforcer run\r32 9 10 1 // enforcer attack\r42 13 10 0 // enforcer death1\r56 10 10 0 // enforcer death2\r67 3 10 0 // enforcer pain1\r71 4 10 0 // enforcer pain2\r76 7 10 0 // enforcer pain3\r84 18 10 0 // enforcer pain4
\ No newline at end of file
--- /dev/null
+Enforcer
+
+Copyright (C) 2004 Greg Mapes
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+7-7-04
+First Release:
+
+Greg Mapes
+
+7-11-04
+
+The gun was a bit long and stuck through a lot of walls and other surfaces when it died, so it got revised.
+
+Greg Mapes
--- /dev/null
+1 17 10 1 // fish attack\r19 19 10 0 // fish death\r41 16 10 1 // fish swim\r59 8 10 0 // fish pain
\ No newline at end of file
--- /dev/null
+All the models in this folder (except zombie.dpm) were copied from the GPL project OpenQuartz 2 (http://www.quakewiki.net/openquartz-2).
\ No newline at end of file
--- /dev/null
+1 8 10 1 // hellknight stand\r10 19 10 1 // hellknight walk\r30 7 10 1 // hellknight run\r38 4 10 0 // hellknight pain\r43 11 10 0 // hellknight death1\r55 8 10 0 // hellknight death2\r64 15 10 0 // hellknight charge1\r80 13 10 0 // hellknight magic1\r94 12 10 0 // hellknight magic2\r107 5 10 0 // hellknight charge2\r113 9 10 1 // hellknight slice\r123 9 10 1 // hellknight smash\r133 21 10 1 // hellknight weapon attack\r155 10 10 0 //hellknight magic3
\ No newline at end of file
--- /dev/null
+Hooded model
+
+Copyright (C) 2003 Ulrich Gablraith
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+History:
+
+7-5-04
+
+The model was given to Mapes so he could port it into OpenQuartz by permission.
+
+Greg Mapes
\ No newline at end of file
--- /dev/null
+1 8 10 1 // knight stand\r10 7 10 1 // knight run\r18 10 10 1 // knight run attack\r29 2 10 0 // knight pain1\r32 10 10 0 // knight pain2\r43 9 10 1 // knight attack\r53 13 10 1 // knight walk\r67 4 10 0 // knight kneel\r72 4 10 1 // knight standing\r77 9 10 0 // knight death1\r88 10 10 0 // knight death2
\ No newline at end of file
--- /dev/null
+Knight model
+
+Copyright (C) 2004 Ulrich Gablraith
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+History:
+
+7-5-04
+
+The model was given to Mapes so he could port it into OpenQuartz by permission.
+
+Greg Mapes
\ No newline at end of file
--- /dev/null
+Monsters -Phantom Knight, Magical Knight, Master Knight, Knight, Jack-o-Lantern,
+ -Doll Fiend.
+
+Copyright (C) 2004 Ulrich Galbraith
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Ulrich Galbraith
+ugalbrai@krl.org
+
+
+7-22-04
+
+First release
+
+I had to give some of them proper animation and proper connecting models so they would work.
+
+-Mapes
\ No newline at end of file
--- /dev/null
+1 8 10 1 // ogre stand\r10 15 10 1 // ogre walk\r26 7 10 1 // ogre run\r34 13 10 1 // ogre swing\r48 13 10 1 // ogre smash\r62 5 10 1 // ogre shoot\r68 4 10 0 // ogre pain1\r73 2 10 0 // ogre pain2\r76 5 10 0 // ogre pain3\r82 15 10 0 // ogre pain4\r98 14 10 0 // ogre pain5\r113 13 10 0 // ogre death1\r127 9 10 0 // ogre death2\r137 10 10 0 // ogre pull
\ No newline at end of file
--- /dev/null
+1 10 10 1 // shalrath attack\r12 4 10 0 // shalrath pain\r18 5 10 0 // shalrath death\r26 12 10 1 // shalrath walk
\ No newline at end of file
--- /dev/null
+1 16 10 1 // shambler stand\r18 11 10 1 // shambler walk\r31 5 10 1 // shambler run\r37 11 10 1 // shambler smash\r49 8 10 1 // shambler swing right\r58 8 10 1 // shambler swing left\r67 11 10 1 // shambler magic\r79 5 10 0 // shambler pain\r85 10 10 0 // shambler death
\ No newline at end of file
--- /dev/null
+1 8 10 1 // ogre stand\r10 15 10 1 // ogre walk\r26 7 10 1 // ogre run\r34 13 10 1 // ogre swing\r48 13 10 1 // ogre smash\r62 5 10 1 // ogre shoot\r68 4 10 0 // ogre pain1\r73 2 10 0 // ogre pain2\r76 5 10 0 // ogre pain3\r82 15 10 0 // ogre pain4\r98 14 10 0 // ogre pain5\r113 13 10 0 // ogre death1\r127 9 10 0 // ogre death2\r137 10 10 0 // ogre pull
\ No newline at end of file
--- /dev/null
+/*
+Generated framegroups file for spider
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 60 60 1 // spider idle
+61 41 120 1 // spider walk
+102 24 60 1 // spider attack
+125 10 60 1 // spider attack2
\ No newline at end of file
--- /dev/null
+This spider model is GPL 2.0 compatible (http://opengameart.org/content/low-poly-spider-glest).
\ No newline at end of file
--- /dev/null
+1 24 10 1 // tarbaby walk\r26 24 10 1 // tarbaby run\r51 5 10 0 // tarbaby jump\r57 3 10 1 // tarbaby fly\r61 1 10 0 // tarbaby explode
\ No newline at end of file
--- /dev/null
+1 14 10 1 // wizard hover\r16 13 10 1 // wizard fly\r30 12 10 1 // wizard magic attack\r43 3 10 0 // wizard pain\r47 7 10 0 // wizard death
\ No newline at end of file
--- /dev/null
+pterascragg model
+
+Copyright (C) 2000 Dylan "lithiumbat" Sartain
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+History:
+
+7-11-00
+
+First release
+build time: about 2 hrs spread over three days
+model was made as a replacement for the scragg or wizard.
+
+7-13-00
+
+Seth Galbraith (sgalbrai@krl.org):
+Lowered death frame to lie on the ground
+Created head gib model
--- /dev/null
+bloodyskull,bloodyskull
+meat,meat
\ No newline at end of file
--- /dev/null
+bloodyskull,bloodyskull_alien
+meat,meat_alien
\ No newline at end of file
--- /dev/null
+bloodyskull,bloodyskull_robot
+meat,meat_robot
\ No newline at end of file
+++ /dev/null
-set g_monster_zombie_respawntime 2
-set g_monster_zombie_movespeed 350
-set g_monster_zombie_health 275
-set g_monster_zombie_stopspeed 90
-set g_monster_zombie_turnspeed 360
-set g_monster_zombie_idle_timer_min 5
-set g_monster_zombie_idle_timer_max 10
-set g_monster_zombie_targetrange 2048
-
-set g_monster_zombie_attack_run_range 128
-set g_monster_zombie_attack_run_hitrange 96
-set g_monster_zombie_attack_run_delay 0.35
-set g_monster_zombie_attack_run_damage 30
-set g_monster_zombie_attack_run_force 300
-
-set g_monster_zombie_attack_stand_range 64
-set g_monster_zombie_attack_stand_hitrange 64
-set g_monster_zombie_attack_stand_delay 0.25
-set g_monster_zombie_attack_stand_damage 60
-set g_monster_zombie_attack_stand_force 250
-
--- /dev/null
+// Misc
+set g_monsters 1 "Enable monsters (master switch)"
+set g_monsters_skill 1 "Monster skill (affecting some of their attributes). 1 - easy, 2 - medium, 3 - hard, 4 - insane, 5 - nightmare"
+set g_monsters_miniboss_chance 5
+set g_monsters_miniboss_healthboost 100
+set g_monsters_forcedrop 0 "Force all monsters to drop this item on death. Use g_monsters_drop_* cvars to change forced drop item"
+set g_monsters_drop_type armor "Type of item to drop when forced. Possible values are: health, armor, ammo"
+set g_monsters_drop_size medium "Size of the item monsters drop. Possible health/amor values are: small, medium, large. Possible ammo values are: shells, bullets, cells, rockets"
+set g_monsters_owners 1 "Monsters will not attack their owners if set to 1"
+set g_monsters_teams 1
+set g_monster_spawnshieldtime 2 "Monsters will not take damage for this amount of seconds"
+set g_monsters_typefrag 1
+set g_monsters_healthbars 1 "Show health bars above monsters"
+set g_monsters_giants_only 0
+set g_monsters_nogiants 1
+set g_monsters_respawn 1 "Enable monster respawning"
+set g_monsters_respawn_delay 20 "Monsters respawn in this amount of seconds"
+set g_monsters_score_kill 1 "Get this many points for killing a naturally spawned monster"
+set g_monsters_max 20 "Global maximum player-spawned monsters"
+set g_monsters_max_perplayer 3 "Maximum monsters per-player"
+set g_monsters_skill_easy 2 "Monster easy skill level (used for skill based functions)"
+set g_monsters_skill_normal 4 "Monster normal skill level (used for skill based functions)"
+set g_monsters_skill_hard 5 "Monster hard skill level (used for skill based functions)"
+set g_monsters_skill_insane 7 "Monster insane skill level (used for skill based functions)"
+set g_monsters_skill_nightmare 10 "Monster nightmare skill level (used for skill based functions)"
+
+// Enforcer
+set g_monster_enforcer 1 "Enable Enforcers"
+set g_monster_enforcer_health 80 "Enforcer health"
+set g_monster_enforcer_drop armor "Enforcer drops this item on death"
+set g_monster_enforcer_drop_size large "Size of the item Enforcers drop. Possible values are: small, medium, large"
+set g_monster_enforcer_speed_walk 75 "Enforcer walk speed"
+set g_monster_enforcer_speed_run 100 "Enforcer run speed"
+set g_monster_enforcer_attack_uzi_bullets 3 "Number of machine gun bullets Enforcer fires"
+
+// Ogre
+set g_monster_ogre 1 "Enable Ogres"
+set g_monster_ogre_health 200 "Ogre health"
+set g_monster_ogre_chainsaw_damage 15 "Ogre chainsaw damage (hits multiple times)"
+set g_monster_ogre_drop ammo "Ogre drops this item on death"
+set g_monster_ogre_drop_size bullets "Size of the item Ogres drop. Possible values are: small, medium, large"
+set g_monster_ogre_speed_walk 100 "Ogre walk speed"
+set g_monster_ogre_speed_run 150 "Ogre run speed"
+set g_monster_ogre_attack_uzi_bullets 3 "Number of machine gun bullets Ogre fires"
+
+// Fiend
+set g_monster_demon 1 "Enable Fiends"
+set g_monster_demon_health 300 "Fiend health"
+set g_monster_demon_attack_jump_damage 40 "Fiend jump attack damage"
+set g_monster_demon_damage 20 "Fiend melee attack damage"
+set g_monster_demon_drop health "Fiend drops this item on death"
+set g_monster_demon_drop_size medium "Size of the item Fiends drop. Possible values are: small, medium, large"
+set g_monster_demon_speed_walk 150 "Fiend walk speed"
+set g_monster_demon_speed_run 300 "Fiend run speed"
+
+// Shambler
+set g_monster_shambler 1 "Enable Shamblers"
+set g_monster_shambler_health 600 "Shambler health"
+set g_monster_shambler_damage 50 "Shambler melee attack damage"
+set g_monster_shambler_attack_lightning_damage 20 "Shambler lightning attack damage per frame"
+set g_monster_shambler_attack_claw_damage 30 "Shambler claw attack damage"
+set g_monster_shambler_drop health "Shambler drops this item on death"
+set g_monster_shambler_drop_size large "Size of the item Shamblers drop. Possible values are: small, medium, large"
+set g_monster_shambler_speed_walk 100 "Shambler walk speed"
+set g_monster_shambler_speed_run 150 "Shambler run speed"
+
+// Knight
+set g_monster_knight 1 "Enable Knights"
+set g_monster_knight_health 75 "Knight Health"
+set g_monster_knight_drop armor "Knight drops this item on death"
+set g_monster_knight_drop_size medium "Size of the item Knights drop. Possible values are: small, medium, large"
+set g_monster_knight_melee_damage 20 "Knight melee attack damage"
+set g_monster_knight_melee_side_damage 10 "Knight melee attack side damage"
+set g_monster_knight_speed_walk 40 "Knight walk speed"
+set g_monster_knight_speed_run 70 "Knight run speed"
+
+// Grunt
+set g_monster_soldier 1 "Enable Grunts"
+set g_monster_soldier_health 100 "Grunt Health"
+set g_monster_soldier_drop ammo "Grunt drops this item on death"
+set g_monster_soldier_drop_size shells "Size of the item Grunts drop. Possible values are: small, medium, large"
+set g_monster_soldier_melee_damage 20 "Grunt melee attack damage"
+set g_monster_soldier_speed_walk 30 "Grunt walk speed"
+set g_monster_soldier_speed_run 50 "Grunt run speed"
+set g_monster_soldier_ammo 5 "Grunt weapon ammo"
+set g_monster_soldier_weapon_laser_chance 6 "Chance of Grunt weapon being laser"
+set g_monster_soldier_weapon_shotgun_chance 8 "Chance of Grunt weapon being shotgun"
+set g_monster_soldier_weapon_machinegun_chance 4 "Chance of Grunt weapon being machine gun"
+set g_monster_soldier_weapon_rocketlauncher_chance 2 "Chance of Grunt weapon being rocket launcher"
+set g_monster_soldier_attack_uzi_bullets 3 "Number of machine gun bullets Grunt fires"
+
+// Scrag
+set g_monster_wizard 1 "Enable Scrags"
+set g_monster_wizard_health 80 "Scrag health"
+set g_monster_wizard_drop ammo "Scrag drops this item on death"
+set g_monster_wizard_drop_size cells "Size of the item Scrags drop. Possible values are: small, medium, large"
+set g_monster_wizard_speed_walk 40 "Scrag walk speed"
+set g_monster_wizard_speed_run 70 "Scrag run speed"
+set g_monster_wizard_spike_damage 15 "Scrag spike damage"
+set g_monster_wizard_spike_damage 7 "Scrag spike edge damage"
+set g_monster_wizard_spike_radius 20 "Scrag spike damage radius"
+set g_monster_wizard_spike_speed 400 "Scrag spike speed"
+
+// Rottweiler
+set g_monster_dog 1 "Enable Rottweilers"
+set g_monster_dog_health 25 "Rottweiler health"
+set g_monster_dog_bite_damage 15 "Rottweiler bite attack damage"
+set g_monster_dog_attack_jump_damage 30 "Rottweiler jump attack damage"
+set g_monster_dog_drop health "Rottweiler drops this item on death"
+set g_monster_dog_drop_size small "Size of the item Rottweilers drop. Possible values are: small, medium, large"
+set g_monster_dog_speed_walk 60 "Rottweiler walk speed"
+set g_monster_dog_speed_run 120 "Rottweiler run speed"
+
+// Spawn
+set g_monster_tarbaby 1 "Enable Spawns"
+set g_monster_tarbaby_health 80 "Spawn health"
+set g_monster_tarbaby_drop ammo "Spawn drops this item when it explodes"
+set g_monster_tarbaby_drop_size rockets "Size of the item Spawns drop. Possible values are: small, medium, large"
+set g_monster_tarbaby_speed_walk 20 "Spawn walk speed"
+set g_monster_tarbaby_speed_run 30 "Spawn run speed"
+
+// Hell-Knight
+set g_monster_hellknight 1 "Enable Hell-Knights"
+set g_monster_hellknight_health 250 "Hell-Knight health"
+set g_monster_hellknight_drop armor "Hell-Knight drops this item on death"
+set g_monster_hellknight_drop_size medium "Size of the item Hell-Knights drop. Possible values are: small, medium, large"
+set g_monster_hellknight_inferno_damage 40 "Hell-Knight inferno damage"
+set g_monster_hellknight_inferno_chance 0.4 "Hell-Knight inferno attack chance"
+set g_monster_hellknight_inferno_damagetime 3 "How long the inferno should burn the player"
+set g_monster_hellknight_fireball_damage 30 "Hell-Knight fireball projectile damage"
+set g_monster_hellknight_fireball_edgedamage 10 "Hell-Knight fireball indirect hit damage"
+set g_monster_hellknight_fireball_force 50 "Hell-Knight fireball projectile push force"
+set g_monster_hellknight_fireball_radius 70 "Hell-Knight fireball projectile damage radius"
+set g_monster_hellknight_fireball_speed 600 "Hell-Knight fireball projectile speed"
+set g_monster_hellknight_fireball_spread 0 "Hell-Knight fireball projectile spread"
+set g_monster_hellknight_fireball_chance 0.3 "Chance for Hell-Knight to throw a fireball"
+set g_monster_hellknight_jump_chance 0.2 "Chance for Hell-Knight to jump at the player (always 1 if enemy is further than _dist)"
+set g_monster_hellknight_jump_damage 25 "Hell-Knight jump attack damage"
+set g_monster_hellknight_jump_dist 500 "Hell-Knight will prioritise jumping if the enemy is this far away"
+set g_monster_hellknight_melee_damage 20 "Hell-Knight melee attack damage"
+set g_monster_hellknight_spike_damage 5 "Hell-Knight spike projectile damage"
+set g_monster_hellknight_spike_edgedamage 5 "Hell-Knight spike projectile indirect hit damage"
+set g_monster_hellknight_spike_radius 20 "Hell-Knight spike projectile damage radius"
+set g_monster_hellknight_spike_force 5 "Hell-Knight spike projectile force"
+set g_monster_hellknight_spike_chance 0.5 "Hell-Knight spike attack chance"
+set g_monster_hellknight_speed_walk 75 "Hell-Knight walk speed"
+set g_monster_hellknight_speed_run 150 "Hell-Knight run speed"
+
+// Rotfish
+set g_monster_fish 1 "Enable Rotfish"
+set g_monster_fish_health 25 "Rotfish health"
+set g_monster_fish_damage 10 "Rotfish bite attack damage"
+set g_monster_fish_drop health "Rotfish drops this item on death"
+set g_monster_fish_drop_size small "Size of the item Rotfish drop. Possible values are: small, medium, large"
+set g_monster_fish_speed_walk 40 "Rotfish walk speed"
+set g_monster_fish_speed_run 70 "Rotfish run speed"
+
+// Vore
+set g_monster_shalrath 1 "Enable Vores"
+set g_monster_shalrath_health 400 "Vore health"
+set g_monster_shalrath_damage 30 "Vore magic attack damage"
+set g_monster_shalrath_drop health "Vore drops this item on death"
+set g_monster_shalrath_drop_size medium "Size of the item Vores drop. Possible values are: small, medium, large"
+set g_monster_shalrath_speed 50 "Vore move speed"
+
+// Spawner
+set g_monster_spawner 1 "Enable Monster Spawner"
+set g_monster_spawner_health 100 "Spawner health"
+set g_monster_spawner_maxmobs 4 "Maximum number of spawned monsters"
+set g_monster_spawner_forcespawn "" "Force spawner to spawn this type of monster"
+
+// Zombie
+set g_monster_zombie 1 "Enable Zombies"
+set g_monster_zombie_attack_leap_damage 45 "Damage when zombie performs an attack leap"
+set g_monster_zombie_attack_leap_delay 1.5 "Delay after zombie attack leap"
+set g_monster_zombie_attack_leap_force 55 "Force of zombie attack leap"
+set g_monster_zombie_attack_leap_range 96 "Range of zombie attack leap"
+set g_monster_zombie_attack_leap_speed 500 "The speed of a zombie attack leap"
+set g_monster_zombie_attack_stand_damage 35 "Damage when zombie hits from a standing position"
+set g_monster_zombie_attack_stand_delay 1.2 "Delay after a zombie hits from a standing position"
+set g_monster_zombie_attack_stand_range 48 "Range of a zombie standing position attack"
+set g_monster_zombie_health 200 "Zombie health"
+set g_monster_zombie_speed_walk 150 "Zombie walk speed"
+set g_monster_zombie_speed_run 400 "Zombie run speed"
+set g_monster_zombie_stopspeed 100 "Speed at which zombie stops"
+set g_monster_zombie_drop health "Zombie drops this item on death"
+set g_monster_zombie_drop_size large "Size of the item zombies drop. Possible values are: small, medium, large"
+
+// Spider
+set g_monster_spider 1 "Enable Spiders"
+set g_monster_spider_attack_type 0 "Spider attack type (0 = ice, 1 = fire, ...)"
+set g_monster_spider_attack_leap_delay 1.5 "Delay after spider attack leap"
+set g_monster_spider_attack_stand_damage 35 "Damage when spider hits from a standing position"
+set g_monster_spider_attack_stand_delay 1.2 "Delay after a spider hits from a standing position"
+set g_monster_spider_health 200 "Spider health"
+set g_monster_spider_idle_timer_min 1 "Minimum time a spider can stay idle"
+set g_monster_spider_speed_walk 150 "Spider walk speed"
+set g_monster_spider_speed_run 400 "Spider run speed"
+set g_monster_spider_stopspeed 100 Speed at which spider stops"
+set g_monster_spider_drop health "Spider drops this item on death"
+set g_monster_spider_drop_size large "Size of the item spiders drop. Possible values are: small, medium, large"
\ No newline at end of file
}
}
}
- } else if (type == DEATH_CUSTOM) {
+ } else if(DEATH_ISMONSTER(type)) {
+ HUD_KillNotify_Push(s1, "", 0, DEATH_GENERIC);
+ if(alsoprint)
+ {
+ if(gentle)
+ print (sprintf(_("^1%s^1 hit a monster, and the monster hit back\n"), s1));
+ else
+ {
+ switch(type)
+ {
+ case DEATH_MONSTER_DEMON_MELEE:
+ print (sprintf(_("^1%s^1 was eviscerated by a Fiend \n"), s1));
+ break;
+ case DEATH_MONSTER_DEMON_JUMP:
+ print (sprintf(_("^1%s^1 didn't see the Fiend pouncing at them \n"), s1));
+ break;
+ case DEATH_MONSTER_SHAMBLER_MELEE:
+ print (sprintf(_("^1%s^1 was smashed by a Shambler \n"), s1));
+ break;
+ case DEATH_MONSTER_SHAMBLER_CLAW:
+ print (sprintf(_("^1%s^1's insides were ripped out by a Shambler \n"), s1));
+ break;
+ case DEATH_MONSTER_SHAMBLER_LIGHTNING:
+ print (sprintf(_("^1%s^1 was zapped to death by a Shambler \n"), s1));
+ break;
+ case DEATH_MONSTER_SOLDIER_NAIL:
+ print (sprintf(_("^1%s^1 was riddled full of holes by a Soldier \n"), s1));
+ break;
+ case DEATH_MONSTER_ENFORCER_NAIL:
+ print (sprintf(_("^1%s^1 was riddled full of holes by an Enforcer \n"), s1));
+ break;
+ case DEATH_MONSTER_DOG_BITE:
+ print (sprintf(_("^1%s^1 was mauled by a Rottweiler \n"), s1));
+ break;
+ case DEATH_MONSTER_DOG_JUMP:
+ print (sprintf(_("^1%s^1 didn't see the pouncing Rottweiler \n"), s1));
+ break;
+ case DEATH_MONSTER_TARBABY_BLOWUP:
+ print (sprintf(_("^1%s^1 was slimed by a Spawn \n"), s1));
+ break;
+ case DEATH_MONSTER_FISH_BITE:
+ print (sprintf(_("^1%s^1 was fed to the Rotfish \n"), s1));
+ break;
+ case DEATH_MONSTER_HELLFISH_BITE:
+ print (sprintf(_("^1%s^1 was eaten alive by a Hellfish \n"), s1));
+ break;
+ case DEATH_MONSTER_SHALRATH_MELEE:
+ print (sprintf(_("^1%s^1 was exploded by a Vore \n"), s1));
+ break;
+ case DEATH_MONSTER_OGRE_CHAINSAW:
+ print (sprintf(_("^1%s^1 was destroyed by an Ogre \n"), s1));
+ break;
+ case DEATH_MONSTER_OGRE_NAIL:
+ print (sprintf(_("^1%s^1 was riddled full of holes by an Ogre \n"), s1));
+ break;
+ case DEATH_MONSTER_ZOMBIE:
+ print (sprintf(_("^1%s^1's brains were eaten by a Zombie \n"), s1));
+ break;
+ case DEATH_MONSTER_HELLKNIGHT_FIREBALL:
+ print (sprintf(_("^1%s^1 was exploded by a Hell-Knight's fireball \n"), s1));
+ case DEATH_MONSTER_MELEE:
+ print (sprintf(_("^1%s^1 was killed by a monster \n"), s1));
+ break;
+ }
+ }
+ }
+ }
+ else if (type == DEATH_CUSTOM) {
HUD_KillNotify_Push(s1, "", 0, DEATH_CUSTOM);
if(alsoprint)
print("^1", sprintf(s2, strcat(s1, "^1")), "\n");
vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
float stat_secrets_found, stat_secrets_total;
- float rows;
+ float stat_current_wave, stat_totalwaves;
+ float stat_monsters_killed, stat_monsters_total;
+ float rows = 0;
string val;
+
+ // get tower defense stats
+ stat_current_wave = getstatf(STAT_CURRENT_WAVE);
+ stat_totalwaves = getstatf(STAT_TOTALWAVES);
+
+ // get monster stats
+ stat_monsters_killed = getstatf(STAT_MONSTERS_KILLED);
+ stat_monsters_total = getstatf(STAT_MONSTERS_TOTAL);
// get secrets stats
stat_secrets_found = getstatf(STAT_SECRETS_FOUND);
stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
// get number of rows
- rows = (stat_secrets_total ? 1 : 0);
+ if(stat_secrets_total)
+ rows += 1;
+ if(stat_totalwaves)
+ rows += 1;
+ if(stat_monsters_total)
+ rows += 1;
// if no rows, return
if not(rows)
else
drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
+
+ // draw waves
+ if(stat_totalwaves)
+ {
+ val = sprintf("%d/%d", stat_current_wave, stat_totalwaves);
+ pos = HUD_DrawKeyValue(pos, _("Current wave:"), val);
+ }
+
+ // draw monsters
+ if(stat_monsters_total)
+ {
+ val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
+ pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
+ }
// draw secrets
- val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
- pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
+ if(stat_secrets_total)
+ {
+ val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
+ pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
+ }
// update position
pos_y += 1.25 * hud_fontsize_y;
const float STAT_RESPAWN_TIME = 72;
+const float STAT_CURRENT_WAVE = 73;
+const float STAT_TOTALWAVES = 74;
+
+const float STAT_MONSTERS_TOTAL = 75;
+const float STAT_MONSTERS_KILLED = 76;
+
// mod stats (1xx)
const float STAT_REDALIVE = 100;
const float STAT_BLUEALIVE = 101;
float DEATH_TURRET_TESLA = 10512;
float DEATH_TURRET_LAST = 10512;
+// Monster death types
+float DEATH_MONSTER = 10513;
+float DEATH_MONSTER_DEMON_MELEE = 10514;
+float DEATH_MONSTER_DEMON_JUMP = 10515;
+float DEATH_MONSTER_SHAMBLER_MELEE = 10516;
+float DEATH_MONSTER_SHAMBLER_CLAW = 10517;
+float DEATH_MONSTER_SHAMBLER_LIGHTNING = 10518;
+float DEATH_MONSTER_SOLDIER_NAIL = 10519;
+float DEATH_MONSTER_ENFORCER_NAIL = 10520;
+float DEATH_MONSTER_DOG_BITE = 10521;
+float DEATH_MONSTER_DOG_JUMP = 10522;
+float DEATH_MONSTER_TARBABY_BLOWUP = 10523;
+float DEATH_MONSTER_FISH_BITE = 10524;
+float DEATH_MONSTER_HELLFISH_BITE = 10525;
+float DEATH_MONSTER_SHALRATH_MELEE = 10526;
+float DEATH_MONSTER_OGRE_CHAINSAW = 10527;
+float DEATH_MONSTER_OGRE_NAIL = 10528;
+float DEATH_MONSTER_MELEE = 10529;
+float DEATH_MONSTER_ZOMBIE = 10530;
+float DEATH_MONSTER_HELLKNIGHT_FIREBALL = 10531;
+float DEATH_MONSTER_LAST = 10532;
+
float DEATH_WEAPONMASK = 0xFF;
float DEATH_HITTYPEMASK = 0x1F00; // which is WAY below 10000 used for normal deaths
float HITTYPE_SECONDARY = 0x100;
#define DEATH_ISWEAPON(t,w) (!DEATH_ISSPECIAL(t) && DEATH_WEAPONOFWEAPONDEATH(t) == (w))
#define DEATH_WEAPONOF(t) (DEATH_ISSPECIAL(t) ? 0 : DEATH_WEAPONOFWEAPONDEATH(t))
#define WEP_VALID(w) ((w) >= WEP_FIRST && (w) <= WEP_LAST)
+#define DEATH_ISMONSTER(t) ((t) >= DEATH_MONSTER && (t) <= DEATH_MONSTER_LAST)
#define FRAGS_PLAYER 0
#define FRAGS_SPECTATOR -666
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
else if(v == "runematch_spawn_point")
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
+ else if(v == "td_generator")
+ MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TD;
else if(v == "target_assault_roundend")
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
else if(v == "onslaught_generator")
REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,"timelimit=20 lives=9 leadlimit=0")
#define g_lms IS_GAMETYPE(LMS)
+REGISTER_GAMETYPE(_("Tower Defense"),td,g_td,TD,"timelimit=0 pointlimit=10 leadlimit=0")
+#define g_td IS_GAMETYPE(TD)
+
REGISTER_GAMETYPE(_("Arena"),arena,g_arena,ARENA,"timelimit=20 pointlimit=10 leadlimit=0")
#define g_arena IS_GAMETYPE(ARENA)
{
if(!inWarmupStage)
if(targ.flags & FL_CLIENT)
+ if(!(attacker.flags & FL_MONSTER)) // no accuracy for monsters
if(targ.deadflag == DEAD_NO)
if(IsDifferentTeam(attacker, targ))
return TRUE;
float autocvar_g_physical_items;
float autocvar_g_physical_items_damageforcescale;
float autocvar_g_physical_items_reset;
+float autocvar_g_td_start_wave;
+float autocvar_g_td_generator_health;
+float autocvar_g_td_current_monsters;
+float autocvar_g_td_generator_damaged_points;
+float autocvar_g_td_monster_count;
+float autocvar_g_td_monster_count_increment;
+float autocvar_g_td_buildphase_time;
+float autocvar_g_td_pvp;
+float autocvar_g_td_max_waves;
+float autocvar_g_td_kill_points;
+float autocvar_g_td_turretkill_points;
+float autocvar_g_td_generator_dontend;
+float autocvar_g_td_force_settings;
+float autocvar_g_td_turret_max;
+float autocvar_g_td_turret_plasma_cost;
+float autocvar_g_td_turret_mlrs_cost;
+float autocvar_g_td_turret_walker_cost;
+float autocvar_g_td_tower_buff_cost;
+float autocvar_g_td_monsters_skill_start;
+float autocvar_g_td_monsters_skill_increment;
+float autocvar_g_td_monsters_spawnshield_time;
+float autocvar_g_td_turret_upgrade_cost;
+float autocvar_g_za_monster_count;
+float autocvar_g_monsters;
+float autocvar_g_monsters_max;
+float autocvar_g_monsters_max_perplayer;
+float autocvar_g_monsters_giants_only;
+float autocvar_g_monsters_typefrag;
+float autocvar_g_monsters_owners;
+float autocvar_g_monsters_miniboss_chance;
+float autocvar_g_monsters_miniboss_healthboost;
+float autocvar_g_monsters_forcedrop;
+string autocvar_g_monsters_drop_type;
+string autocvar_g_monsters_drop_size;
+float autocvar_g_monsters_teams;
+float autocvar_g_monsters_healthbars;
+float autocvar_g_monsters_respawn_delay;
+float autocvar_g_monsters_respawn;
+float autocvar_g_monsters_nogiants;
+float autocvar_g_monsters_skill_easy;
+float autocvar_g_monsters_skill_normal;
+float autocvar_g_monsters_skill_hard;
+float autocvar_g_monsters_skill_insane;
+float autocvar_g_monsters_skill_nightmare;
self.event_damage = PlayerDamage;
self.bot_attack = TRUE;
+ self.monster_attack = TRUE;
self.statdraintime = time + 5;
self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = 0;
self.target = s;
activator = world;
self = oldself;
+
+ Unfreeze(self);
spawn_spot = spot;
MUTATOR_CALLHOOK(PlayerSpawn);
return;
#endif
+ if(self.frozen)
+ {
+ self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
+ self.health = max(1, self.revive_progress * autocvar_g_balance_health_start);
+
+ if(self.revive_progress >= 1)
+ Unfreeze(self);
+ }
+
MUTATOR_CALLHOOK(PlayerPreThink);
if(!self.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
self.prevorigin = self.origin;
if (!self.vehicle)
- if (((self.BUTTON_CROUCH && !self.hook.state) || self.health <= g_bloodloss) && self.animstate_startframe != self.anim_melee_x && !self.freezetag_frozen) // prevent crouching if using melee attack
+ if (((self.BUTTON_CROUCH && !self.hook.state) || self.health <= g_bloodloss) && self.animstate_startframe != self.anim_melee_x && !self.freezetag_frozen && !self.frozen) // prevent crouching if using melee attack
{
if (!self.crouch)
{
// secret status
secrets_setstatus();
+ // monsters status
+ monsters_setstatus();
+
self.dmg_team = max(0, self.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
//self.angles_y=self.v_angle_y + 90; // temp
*/
void PlayerJump (void)
{
- if(self.freezetag_frozen)
+ if(self.freezetag_frozen || self.frozen)
return; // no jumping in freezetag when frozen
float mjumpheight;
self.disableclientprediction = 0;
if(time < self.ladder_time)
self.disableclientprediction = 1;
+
+ if(self.frozen)
+ {
+ self.movement = '0 0 0';
+ self.disableclientprediction = 1;
+ }
MUTATOR_CALLHOOK(PlayerPhysics);
PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
}
}
- else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.freezetag_frozen)
+ else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.freezetag_frozen && !self.frozen)
{
//makevectors(self.v_angle_y * '0 1 0');
makevectors(self.v_angle);
if (!self.animstate_override)
{
- if (self.freezetag_frozen)
+ if (self.freezetag_frozen || self.frozen)
setanim(self, self.anim_idle, TRUE, FALSE, FALSE);
else if (!(self.flags & FL_ONGROUND) || self.BUTTON_JUMP)
{
w = self.weapon;
if (w == 0)
return; // just in case
+ if(self.frozen)
+ return;
if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon))
return;
if(!autocvar_g_weapon_throwable)
if(((arena_roundbased || g_ca || g_freezetag) && time < warmup) || ((time < game_starttime) && !autocvar_sv_ready_restart_after_countdown))
return;
- if(self.freezetag_frozen == 1)
+ if(self.freezetag_frozen == 1 || self.frozen == 1)
return;
if (!self.weaponentity || self.health < 1)
vector org;
float lag;
- if(player.hitplotfh >= 0)
+ if(player.hitplotfh >= 0 && !(player.flags & FL_MONSTER))
{
lag = ANTILAG_LATENCY(player);
if(lag < 0.001)
void W_AttachToShotorg(entity flash, vector offset)
{
+ if(self.flags & FL_MONSTER)
+ return; // no flash for monsters
entity xflash;
flash.owner = self;
flash.angles_z = random() * 360;
void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload)
{
+ if(self.flags & FL_MONSTER) // no ammo for monsters... yet
+ return;
+
if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
return;
}
}
+void ClientCommand_mobspawn(float request, float argc)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ entity e;
+ string tospawn, mname;
+ float moveflag;
+
+ moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
+ tospawn = argv(1);
+ mname = argv(3);
+
+ if(tospawn == "list")
+ {
+ sprint(self, "Available monsters:\n");
+ sprint(self, strcat(monsterlist(), "\n"));
+ return;
+ }
+
+ if(self.classname != STR_PLAYER) { sprint(self, "You can't spawn monsters while spectating.\n"); }
+ else if(self.deadflag) { sprint(self, "You can't spawn monsters while dead.\n"); }
+ else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); }
+ else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); }
+ else
+ {
+ // all worked out, so continue
+ self.monstercount += 1;
+ totalspawned += 1;
+
+ makevectors(self.v_angle);
+ WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+
+ e = spawnmonster(tospawn, self, self, trace_endpos, FALSE, moveflag);
+ if(mname != "") e.netname = strzone(mname);
+
+ sprint(self, strcat("Spawned 1 ", tospawn, "\n"));
+ }
+
+ return;
+ }
+
+ default:
+ sprint(self, "Incorrect parameters for ^2mobspawn^7\n");
+ case CMD_REQUEST_USAGE:
+ {
+ sprint(self, "\nUsage:^3 cmd mobspawn monster\n");
+ sprint(self, " See 'cmd mobspawn list' for available arguments.\n");
+ return;
+ }
+ }
+}
+
void ClientCommand_ready(float request) // todo: anti-spam for toggling readyness
{
switch(request)
CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \
CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
+ CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \
CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
.float lms_spectate_warning;
.float checkfail;
+// number of monsters spawned with mobspawn command
+float totalspawned;
+
string MapVote_Suggest(string m);
+entity spawnmonster(string monster, entity spawnedby, entity own, vector orig, float respwn, float moveflag);
+
// used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file
-void ClientCommand_macro_write_aliases(float fh);
\ No newline at end of file
+void ClientCommand_macro_write_aliases(float fh);
}
}
+void GameCommand_butcher(float request)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ float removed_count = 0;
+ totalspawned = 0;
+ entity montokill, head;
+ FOR_EACH_MONSTER(montokill)
+ {
+ if(montokill.sprite)
+ WaypointSprite_Kill(montokill.sprite);
+
+ remove(montokill);
+ removed_count += 1;
+ }
+
+ FOR_EACH_PLAYER(head)
+ {
+ head.monstercount = 0;
+ }
+ totalspawned = 0;
+
+ if(removed_count <= 0)
+ print("No monsters to kill\n");
+ else
+ print(strcat("Killed ", ftos(removed_count), " monster", ((removed_count == 1) ? "\n" : "s\n")));
+ return; // never fall through to usage
+ }
+
+ default:
+ case CMD_REQUEST_USAGE:
+ {
+ print("\nUsage:^3 sv_cmd butcher\n");
+ print(" No arguments required.\n");
+ return;
+ }
+ }
+}
+
void GameCommand_allready(float request)
{
switch(request)
// Common commands have double indentation to separate them a bit.
#define SERVER_COMMANDS(request,arguments,command) \
SERVER_COMMAND("adminmsg", GameCommand_adminmsg(request, arguments), "Send an admin message to a client directly") \
+ SERVER_COMMAND("butcher", GameCommand_butcher(request), "Instantly removes all monsters on the map") \
SERVER_COMMAND("allready", GameCommand_allready(request), "Restart the server and reset the players") \
SERVER_COMMAND("allspec", GameCommand_allspec(request, arguments), "Force all players to spectate") \
SERVER_COMMAND("anticheat", GameCommand_anticheat(request, arguments), "Create an anticheat report for a client") \
.float freezetag_frozen;
.float freezetag_revive_progress;
+.float frozen; // for freeze attacks
+.float revive_progress;
+.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
+
.entity muzzle_flash;
.float misc_bulletcounter; // replaces uzi & hlac bullet counter.
#define MISSILE_IS_CONFUSABLE(m) ((m.missile_flags & MIF_GUIDED_CONFUSABLE) ? TRUE : FALSE)
#define MISSILE_IS_GUIDED(m) ((m.missile_flags & MIF_GUIDED_ALL) ? TRUE : FALSE)
#define MISSILE_IS_TRACKING(m) ((m.missile_flags & MIF_GUIDED_TRACKING) ? TRUE : FALSE)
+
+.string spawnmob;
+.float monster_attack;
+
+float monster_skill;
+float spawncode_first_load; // used to tell the player the monster database is loading (TODO: fix this?)
+
+.entity monster_owner; // new monster owner entity, fixes non-solid monsters
+.float monstercount; // per player monster count
+
+.float stat_monsters_killed; // stats
+.float stat_monsters_total;
+float monsters_total;
+float monsters_killed;
+void monsters_setstatus(); // monsters.qc
+string monsterlist();
}
}
+void Ice_Think()
+{
+ if(self.owner.health < 1)
+ {
+ remove(self);
+ return;
+ }
+ setorigin(self, self.owner.origin - '0 0 16');
+ self.nextthink = time;
+}
+
+void Freeze (entity targ, float freeze_time)
+{
+ float monster = (targ.flags & FL_MONSTER);
+ float player = (targ.flags & FL_CLIENT);
+
+ if(!player && !monster) // only specified entities can be freezed
+ return;
+
+ if(targ.frozen || targ.freezetag_frozen)
+ return;
+
+ targ.frozen = 1;
+ targ.revive_progress = 0;
+ targ.health = 1;
+ targ.revive_speed = freeze_time;
+
+ entity ice;
+ ice = spawn();
+ ice.owner = targ;
+ ice.classname = "ice";
+ ice.scale = targ.scale;
+ ice.think = Ice_Think;
+ ice.nextthink = time;
+ ice.frame = floor(random() * 21); // ice model has 20 different looking frames
+ setmodel(ice, "models/ice/ice.md3");
+
+ entity oldself;
+ oldself = self;
+ self = ice;
+ Ice_Think();
+ self = oldself;
+
+ RemoveGrapplingHook(targ);
+}
+
+void Unfreeze (entity targ)
+{
+ targ.frozen = 0;
+ targ.revive_progress = 0;
+ targ.health = ((targ.classname == STR_PLAYER) ? autocvar_g_balance_health_start : targ.max_health);
+
+ // remove the ice block
+ entity ice;
+ for(ice = world; (ice = find(ice, classname, "ice")); ) if(ice.owner == targ)
+ {
+ remove(ice);
+ break;
+ }
+}
+
// these are updated by each Damage call for use in button triggering and such
entity damage_targ;
entity damage_inflictor;
mirrorforce *= g_weaponforcefactor;
}
+ if(targ.frozen && attacker.classname != "monster_spider")
+ {
+ damage = 0;
+ force *= 0.2;
+ }
+
// should this be changed at all? If so, in what way?
frag_attacker = attacker;
frag_target = targ;
else
victim = targ;
- if(victim.classname == "player" || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ if(victim.classname == "player" || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET || victim.flags & FL_MONSTER)
{
if(IsDifferentTeam(victim, attacker))
{
e.fire_endtime = 0;
// ice stops fire
- if(e.freezetag_frozen)
+ if(e.freezetag_frozen || e.frozen)
e.fire_endtime = 0;
t = min(frametime, e.fire_endtime - time);
if((arena_roundbased && time < warmup) || (time < game_starttime))
return;
- if(self.freezetag_frozen)
+ if(self.freezetag_frozen || self.frozen)
return;
if(self.vehicle)
BADCVAR("g_freezetag");
BADCVAR("g_keepaway");
BADCVAR("g_keyhunt");
+ BADCVAR("g_td");
BADCVAR("g_keyhunt_teams");
BADCVAR("g_keyhunt_teams");
BADCVAR("g_lms");
addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
}
+
+ if(g_td)
+ {
+ addstat(STAT_CURRENT_WAVE, AS_FLOAT, stat_current_wave);
+ addstat(STAT_TOTALWAVES, AS_FLOAT, stat_totalwaves);
+ }
+
+ // freeze attacks
+ addstat(STAT_FROZEN, AS_INT, frozen);
+ addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress);
// g_movementspeed hack
addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
// secrets
addstat(STAT_SECRETS_TOTAL, AS_FLOAT, stat_secrets_total);
addstat(STAT_SECRETS_FOUND, AS_FLOAT, stat_secrets_found);
+
+ // monsters
+ addstat(STAT_MONSTERS_TOTAL, AS_FLOAT, stat_monsters_total);
+ addstat(STAT_MONSTERS_KILLED, AS_FLOAT, stat_monsters_killed);
// misc
addstat(STAT_RESPAWN_TIME, AS_FLOAT, stat_respawn_time);
// weird mutators that deserve to count as mod
if(autocvar_g_minstagib)
modname = "MinstaGib";
+ if(autocvar_g_monsters)
+ modname = "Monsters";
// extra mutators that deserve to count as mod
MUTATOR_CALLHOOK(SetModname);
// weird game types that deserve to count as mod
return WINNING_NO;
}
+// TD winning condition:
+// game terminates if there are no generators (or 1 dies if td_dontend is TRUE)
+float gensurvived;
+float WinningCondition_TowerDefense()
+{
+ WinningConditionHelper(); // set worldstatus
+
+ if(inWarmupStage)
+ return WINNING_NO;
+
+ // first check if the game has ended
+ if(gendestroyed == TRUE) // FALSE means either generator hasen't spawned yet, or mapper didn't add one
+ if(td_gencount < 1 || !td_dont_end)
+ {
+ ClearWinners();
+ dprint("Everyone lost, ending game.\n");
+ return WINNING_YES;
+ }
+
+ if(gensurvived)
+ {
+ ClearWinners();
+ SetWinners(winning, 4);
+ return WINNING_YES;
+ }
+
+ // Two or more teams remain
+ return WINNING_NO;
+}
+
/*
============
CheckRules_World
{
checkrules_status = WinningCondition_Onslaught(); // TODO remove this?
}
+ else if(g_td)
+ {
+ checkrules_status = WinningCondition_TowerDefense(); // TODO make these mutator hooks?
+ }
else
{
checkrules_status = WinningCondition_Scores(fraglimit, leadlimit);
#define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(clienttype(v) == CLIENTTYPE_REAL)
#define FOR_EACH_PLAYER(v) for(v = world; (v = find(v, classname, STR_PLAYER)) != world; )
#define FOR_EACH_REALPLAYER(v) FOR_EACH_PLAYER(v) if(clienttype(v) == CLIENTTYPE_REAL)
+#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
#else
#define FOR_EACH_CLIENTSLOT(v) for(v = world; (v = nextent(v)) && (num_for_edict(v) <= maxclients); )
#define FOR_EACH_CLIENT(v) FOR_EACH_CLIENTSLOT(v) if(v.flags & FL_CLIENT)
#define FOR_EACH_PLAYER(v) FOR_EACH_CLIENT(v) if(v.classname == STR_PLAYER)
#define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if(v.classname != STR_PLAYER)
#define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(v.classname == STR_PLAYER)
+#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
#endif
#define CENTER_OR_VIEWOFS(ent) (ent.origin + ((ent.classname == STR_PLAYER) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
void readlevelcvars(void)
{
g_minstagib = cvar("g_minstagib");
+
+ monster_skill = cvar("g_monsters_skill");
// load ALL the mutators
if(cvar("g_dodging"))
// is this a mutator? is this a mode?
if(cvar("g_sandbox"))
MUTATOR_ADD(sandbox);
+
+ if(cvar("g_za"))
+ MUTATOR_ADD(mutator_zombie_apocalypse);
if(cvar("sv_allow_fullbright"))
serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
--- /dev/null
+.float sprite_height;
+
+.void() attack_melee;
+.float() attack_ranged;
+.float() checkattack;
+
+entity(entity ent) FindTarget;
+
+.float spawner_monstercount;
+
+.float monster_respawned; // used to make sure we're not recounting respawned monster stats
+
+float monsters_spawned;
+
+const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 2
+const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 3
+const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill 4
+const float MONSTERSKILL_NOTINSANE = 2048; // monster will not spawn on skill 5
+const float MONSTERSKILL_NOTNIGHTMARE = 4096; // monster will not spawn on skill >= 6
+
+const float MONSTERFLAG_NORESPAWN = 2;
+const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_NOWANDER = 128; // disable wandering around (currently unused)
+const float MONSTERFLAG_APPEAR = 256; // delay spawn until triggered
+const float MONSTERFLAG_GIANT = 512; // experimental giant monsters feature
+const float MONSTERFLAG_SPAWNED = 1024; // flag for spawned monsters
+
+.void() monster_spawnfunc;
+.void() monster_die;
+.void() monster_delayedattack;
+
+.float monster_moveflags; // checks where to move when not attacking
+.float monster_movestate; // used to tell what the monster is currently doing
+const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
+const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
+const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
+const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
+const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate
+
+float enemy_range () { return vlen(self.enemy.origin - self.origin); }
+
+float MONSTER_STATE_ATTACK_LEAP = 1; // the start of something big?
--- /dev/null
+// TODO: clean up this file?
+
+void M_Item_Touch ()
+{
+ if(self && other.classname == STR_PLAYER && other.deadflag == DEAD_NO)
+ {
+ Item_Touch();
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.1;
+ }
+}
+
+void Monster_DropItem (string itype, string itemsize)
+{
+ if(itype == "0")
+ return; // someone didnt want an item...
+ vector backuporigin = self.origin + ((self.mins + self.maxs) * 0.5);
+ entity oldself;
+
+ oldself = self;
+ self = spawn();
+
+ if (itype == "armor")
+ {
+ if(itemsize == "large") spawnfunc_item_armor_large();
+ else if (itemsize == "small") spawnfunc_item_armor_small();
+ else if (itemsize == "medium") spawnfunc_item_armor_medium();
+ else print("Invalid monster drop item selected.\n");
+ }
+ else if (itype == "health")
+ {
+ if(itemsize == "large") spawnfunc_item_health_large();
+ else if (itemsize == "small") spawnfunc_item_health_small();
+ else if (itemsize == "medium") spawnfunc_item_health_medium();
+ else if (itemsize == "mega") spawnfunc_item_health_mega();
+ else print("Invalid monster drop item selected.\n");
+ }
+ else if (itype == "ammo")
+ {
+ if(itemsize == "shells") spawnfunc_item_shells();
+ else if (itemsize == "cells") spawnfunc_item_cells();
+ else if (itemsize == "bullets") spawnfunc_item_bullets();
+ else if (itemsize == "rockets") spawnfunc_item_rockets();
+ else print("Invalid monster drop item selected.\n");
+ }
+
+ self.velocity = randomvec() * 175 + '0 0 325';
+
+ self.gravity = 1;
+ self.origin = backuporigin;
+
+ self.touch = M_Item_Touch;
+
+ SUB_SetFade(self, time + 5, 1);
+
+ self = oldself;
+}
+
+float monster_isvalidtarget (entity targ, entity ent, float neutral)
+{
+ if(!targ || !ent)
+ return FALSE; // this check should fix a crash
+
+ if(targ.vehicle_flags & VHF_ISVEHICLE)
+ targ = targ.vehicle;
+
+ if(time < game_starttime)
+ return FALSE; // monsters do nothing before the match has started
+
+ traceline(ent.origin, targ.origin, FALSE, ent);
+
+ if(vlen(targ.origin - ent.origin) >= 2000)
+ return FALSE; // enemy is too far away
+
+ if(trace_ent != targ)
+ return FALSE; // we can't see the enemy
+
+ if(neutral == TRUE)
+ return TRUE; // we come in peace!
+
+ if(targ.takedamage == DAMAGE_NO)
+ return FALSE; // enemy can't be damaged
+
+ if(targ.items & IT_INVISIBILITY)
+ return FALSE; // enemy is invisible
+
+ if(targ.classname == STR_SPECTATOR || targ.classname == STR_OBSERVER)
+ return FALSE; // enemy is a spectator
+
+ if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
+ return FALSE; // enemy/self is dead
+
+ if(targ.monster_owner == ent || ent.monster_owner == targ)
+ return FALSE; // enemy owns us, or we own them
+
+ if(targ.flags & FL_NOTARGET)
+ return FALSE; // enemy can't be targetted
+
+ if not(autocvar_g_monsters_typefrag)
+ if(targ.BUTTON_CHAT)
+ return FALSE; // no typefragging!
+
+ if(teamplay)
+ if(targ.team == ent.team)
+ return FALSE; // enemy is on our team
+
+ return TRUE;
+}
+
+void MonsterTouch ()
+{
+ if(other == world)
+ return;
+
+ if(self.enemy != other)
+ if not(other.flags & FL_MONSTER)
+ if(monster_isvalidtarget(other, self, FALSE))
+ self.enemy = other;
+}
+
+void monster_melee (entity targ, float damg, float er, float deathtype)
+{
+ float bigdmg = 0, rdmg = damg * random();
+
+ if (self.health <= 0)
+ return;
+ if (targ == world)
+ return;
+
+ if (vlen(self.origin - targ.origin) > er * self.scale)
+ return;
+
+ bigdmg = rdmg * self.scale;
+
+ if(random() < 0.01) // critical hit ftw
+ bigdmg = 200;
+
+ Damage(targ, self, self, bigdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
+}
+
+void Monster_CheckDropCvars (string mon)
+{
+ string dropitem;
+ string dropsize;
+
+ dropitem = cvar_string(strcat("g_monster_", mon, "_drop"));
+ dropsize = cvar_string(strcat("g_monster_", mon, "_drop_size"));
+
+ monster_dropitem = dropitem;
+ monster_dropsize = dropsize;
+ MUTATOR_CALLHOOK(MonsterDropItem);
+ dropitem = monster_dropitem;
+ dropsize = monster_dropsize;
+
+ if(autocvar_g_monsters_forcedrop)
+ Monster_DropItem(autocvar_g_monsters_drop_type, autocvar_g_monsters_drop_size);
+ else if(dropitem != "")
+ Monster_DropItem(dropitem, dropsize);
+ else
+ Monster_DropItem("armor", "medium");
+}
+
+void ScaleMonster (float scle)
+{
+ // this should prevent monster from falling through floor when scale changes
+ self.scale = scle;
+ setorigin(self, self.origin + ('0 0 30' * scle));
+}
+
+void Monster_CheckMinibossFlag ()
+{
+ if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
+ return;
+
+ float healthboost = autocvar_g_monsters_miniboss_healthboost;
+ float r = random() * 4;
+
+ // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
+ if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (random() * 100 < autocvar_g_monsters_miniboss_chance))
+ {
+ if (r < 2 || self.team == COLOR_TEAM2)
+ {
+ self.strength_finished = -1;
+ healthboost *= monster_skill;
+ self.effects |= (EF_FULLBRIGHT | EF_BLUE);
+ }
+ else if (r >= 1 || self.team == COLOR_TEAM1)
+ {
+ self.invincible_finished = -1;
+ healthboost *= bound(0.5, monster_skill, 1.5);
+ self.effects |= (EF_FULLBRIGHT | EF_RED);
+ }
+ self.health += healthboost;
+ self.cnt += 20;
+ ScaleMonster(1.5);
+ self.flags |= MONSTERFLAG_MINIBOSS;
+ if(teamplay && autocvar_g_monsters_teams)
+ return;
+ do
+ {
+ self.colormod_x = random();
+ self.colormod_y = random();
+ self.colormod_z = random();
+ self.colormod = normalize(self.colormod);
+ }
+ while (self.colormod_x > 0.6 && self.colormod_y > 0.6 && self.colormod_z > 0.6);
+ }
+}
+
+void Monster_Fade ()
+{
+ if not(self.spawnflags & MONSTERFLAG_NORESPAWN)
+ if(autocvar_g_monsters_respawn)
+ {
+ self.monster_respawned = TRUE;
+ setmodel(self, "");
+ self.think = self.monster_spawnfunc;
+ self.nextthink = time + autocvar_g_monsters_respawn_delay;
+ setorigin(self, self.pos1);
+ self.angles = self.pos2;
+ self.health = 0;
+ return;
+ }
+ self.think = SUB_Remove;
+ self.nextthink = time + 4;
+ SUB_SetFade(self, time + 3, 1);
+}
+
+float Monster_CanJump (vector vel)
+{
+ local vector old = self.velocity;
+
+ self.velocity = vel;
+ tracetoss(self, self);
+ self.velocity = old;
+ if (trace_ent != self.enemy)
+ return FALSE;
+
+ return TRUE;
+}
+
+float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished)
+{
+ if not(self.flags & FL_ONGROUND)
+ return FALSE;
+ if(self.health < 1)
+ return FALSE; // called when dead?
+ if not(Monster_CanJump(vel))
+ return FALSE;
+
+ self.frame = anm;
+ self.state = MONSTER_STATE_ATTACK_LEAP;
+ self.touch = touchfunc;
+ self.origin_z += 1;
+ self.velocity = vel;
+ if (self.flags & FL_ONGROUND)
+ self.flags -= FL_ONGROUND;
+
+ self.attack_finished_single = time + anim_finished;
+
+ return TRUE;
+}
+
+float GenericCheckAttack ()
+{
+ // checking attack while dead?
+ if (self.health <= 0 || self.enemy == world)
+ return FALSE;
+
+ if(self.monster_delayedattack && self.delay != -1)
+ {
+ if(time < self.delay)
+ return FALSE;
+
+ self.monster_delayedattack();
+ }
+
+ if (time < self.attack_finished_single)
+ return FALSE;
+
+ if (enemy_range() > 2000) // long traces are slow
+ return FALSE;
+
+ if(self.attack_melee)
+ if(enemy_range() <= 100 * self.scale)
+ {
+ self.attack_melee(); // don't wait for nextthink - too slow
+ return TRUE;
+ }
+
+ // monster doesn't have a ranged attack function, so stop here
+ if(!self.attack_ranged)
+ return FALSE;
+
+ // see if any entities are in the way of the shot
+ if (!findtrajectorywithleading(self.origin, '0 0 0', '0 0 0', self.enemy, 800, 0, 2.5, 0, self))
+ return FALSE;
+
+ self.attack_ranged(); // don't wait for nextthink - too slow
+ return TRUE;
+}
+
+void monster_use ()
+{
+ if (self.enemy)
+ return;
+ if (self.health <= 0)
+ return;
+
+ if(!monster_isvalidtarget(activator, self, -1))
+ return;
+
+ self.enemy = activator;
+}
+
+float trace_path(vector from, vector to)
+{
+ vector dir = normalize(to - from) * 15, offset = '0 0 0';
+ float trace1 = trace_fraction;
+
+ offset_x = dir_y;
+ offset_y = -dir_x;
+ traceline (from+offset, to+offset, TRUE, self);
+
+ traceline(from-offset, to-offset, TRUE, self);
+
+ return ((trace1 < trace_fraction) ? trace1 : trace_fraction);
+}
+
+vector monster_pickmovetarget(entity targ)
+{
+ // enemy is always preferred target
+ if(self.enemy)
+ {
+ self.monster_movestate = MONSTER_MOVE_ENEMY;
+ return self.enemy.origin;
+ }
+
+ switch(self.monster_moveflags)
+ {
+ case MONSTER_MOVE_OWNER:
+ {
+ self.monster_movestate = MONSTER_MOVE_OWNER;
+ if(self.monster_owner && self.monster_owner.classname != "monster_swarm")
+ return self.monster_owner.origin;
+ }
+ case MONSTER_MOVE_WANDER:
+ {
+ self.monster_movestate = MONSTER_MOVE_WANDER;
+ if(targ)
+ return targ.origin;
+
+ self.angles_y = random() * 500;
+ makevectors(self.angles);
+ return self.origin + v_forward * 600;
+ }
+ case MONSTER_MOVE_SPAWNLOC:
+ {
+ self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
+ return self.pos1;
+ }
+ default:
+ case MONSTER_MOVE_NOMOVE:
+ {
+ self.monster_movestate = MONSTER_MOVE_NOMOVE;
+ return self.origin;
+ }
+ }
+}
+
+.float last_trace;
+void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
+{
+ if(self.target)
+ self.goalentity = find(world, targetname, self.target);
+
+ float l = vlen(self.moveto - self.origin);
+ float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10');
+ float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15');
+ entity targ = self.goalentity;
+
+ if(self.frozen)
+ {
+ self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
+ self.health = max(1, self.revive_progress * self.max_health);
+
+ if(self.sprite)
+ {
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
+
+ self.velocity = '0 0 0';
+ self.enemy = world;
+ if(self.revive_progress >= 1)
+ Unfreeze(self); // wait for next think before attacking
+ self.nextthink = time + 0.1;
+
+ return; // no moving while frozen
+ }
+
+ if(self.flags & FL_SWIM)
+ {
+ if(self.waterlevel < WATERLEVEL_WETFEET)
+ {
+ if(time < self.last_trace)
+ return;
+ self.last_trace = time + 0.4;
+ self.angles = '0 0 -90';
+ Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
+ if(random() < 0.5)
+ {
+ self.velocity_y += random() * 50;
+ self.velocity_x -= random() * 50;
+ }
+ else
+ {
+ self.velocity_y -= random() * 50;
+ self.velocity_x += random() * 50;
+ }
+ self.velocity_z += random()*150;
+ if (self.flags & FL_ONGROUND)
+ self.flags -= FL_ONGROUND;
+ self.movetype = MOVETYPE_BOUNCE;
+ self.velocity_z = -200;
+ return;
+ }
+ else
+ {
+ self.angles = '0 0 0';
+ self.movetype = MOVETYPE_WALK;
+ }
+ }
+
+ if(gameover || time < game_starttime)
+ {
+ runspeed = walkspeed = 0;
+ self.frame = manim_idle;
+ movelib_beak_simple(stopspeed);
+ return;
+ }
+
+ runspeed *= monster_skill;
+ walkspeed *= monster_skill;
+
+ monster_target = targ;
+ monster_speed_run = runspeed;
+ monster_speed_walk = walkspeed;
+ MUTATOR_CALLHOOK(MonsterMove);
+ targ = monster_target;
+ runspeed = monster_speed_run;
+ walkspeed = monster_speed_walk;
+
+ if(IsDifferentTeam(self.monster_owner, self))
+ self.monster_owner = world;
+
+ if(self.enemy.health <= 0 || (!autocvar_g_monsters_typefrag && self.enemy.BUTTON_CHAT))
+ self.enemy = world;
+
+ if not(self.enemy)
+ self.enemy = FindTarget(self);
+
+ if(time >= self.last_trace)
+ {
+ if(self.monster_movestate == MONSTER_MOVE_WANDER && self.goalentity.classname != "td_waypoint")
+ self.last_trace = time + 2;
+ else
+ self.last_trace = time + 0.5;
+ self.moveto = monster_pickmovetarget(targ);
+ }
+
+ vector angles_face = vectoangles(self.moveto - self.origin);
+ self.angles_y = angles_face_y;
+
+ if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
+ {
+ self.state = 0;
+ self.touch = MonsterTouch;
+ }
+
+ v_forward = normalize(self.moveto - self.origin);
+
+ if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8))
+ if(self.flags & FL_ONGROUND)
+ movelib_jump_simple(100);
+
+ if(vlen(self.origin - self.moveto) > 64)
+ {
+ if(self.flags & FL_FLY)
+ movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+ else
+ movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+ if(time > self.pain_finished)
+ if(time > self.attack_finished_single)
+ self.frame = ((self.enemy) ? manim_run : manim_walk);
+ }
+ else
+ {
+ movelib_beak_simple(stopspeed);
+ if(time > self.attack_finished_single)
+ if(time > self.pain_finished)
+ if (vlen(self.velocity) <= 30)
+ self.frame = manim_idle;
+ }
+
+ if(self.enemy)
+ {
+ if(!self.checkattack)
+ return; // to stop other code from crashing here
+
+ self.checkattack();
+ }
+}
+
+void monsters_setstatus()
+{
+ self.stat_monsters_total = monsters_total;
+ self.stat_monsters_killed = monsters_killed;
+}
+
+
+/*
+===================
+
+Monster spawn code
+
+===================
+*/
+
+void Monster_Appear ()
+{
+ self.enemy = activator;
+ self.spawnflags &~= MONSTERFLAG_APPEAR;
+ self.monster_spawnfunc();
+}
+
+entity FindTarget (entity ent)
+{
+ if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
+ local entity e;
+ for(e = world; (e = findflags(e, monster_attack, TRUE)); )
+ {
+ if(monster_isvalidtarget(e, ent, FALSE))
+ {
+ return e;
+ }
+ }
+ return world;
+}
+
+void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(self.frozen)
+ return;
+
+ if(monster_isvalidtarget(attacker, self, FALSE))
+ self.enemy = attacker;
+
+ self.health -= damage;
+
+ if(self.sprite)
+ {
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
+
+ self.dmg_time = time;
+
+ if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
+ spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME: PLACEHOLDER
+
+ if(self.damageforcescale < 1 && self.damageforcescale > 0)
+ self.velocity += force * self.damageforcescale;
+ else
+ self.velocity += force;
+
+ if(deathtype != DEATH_DROWN)
+ {
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+ if (damage > 50)
+ Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
+ if (damage > 100)
+ Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
+ }
+
+ if(self.health <= 0)
+ {
+ if(self.sprite)
+ {
+ // Update one more time to avoid waypoint fading without emptying healthbar
+ WaypointSprite_UpdateHealth(self.sprite, 0);
+ }
+
+ if(self.flags & MONSTERFLAG_MINIBOSS) // TODO: cvarise the weapon drop?
+ W_ThrowNewWeapon(self, WEP_NEX, 0, self.origin, self.velocity);
+
+ activator = attacker;
+ other = self.enemy;
+ self.target = self.target2;
+ self.target2 = "";
+ SUB_UseTargets();
+
+ self.monster_die();
+ }
+}
+
+// used to hook into monster post death functions without a mutator
+void monster_hook_death()
+{
+ if(self.sprite)
+ WaypointSprite_Kill(self.sprite);
+
+ if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
+ monsters_killed += 1;
+
+ if(self.realowner.classname == "monster_spawner")
+ self.realowner.spawner_monstercount -= 1;
+
+ if(self.realowner.flags & FL_CLIENT)
+ self.realowner.monstercount -= 1;
+
+ totalspawned -= 1;
+
+ MUTATOR_CALLHOOK(MonsterDies);
+}
+
+// used to hook into monster post spawn functions without a mutator
+void monster_hook_spawn()
+{
+ self.health *= monster_skill; // skill based monster health?
+ self.max_health = self.health;
+
+ if(teamplay && autocvar_g_monsters_teams)
+ {
+ self.colormod = TeamColor(self.team);
+ self.monster_attack = TRUE;
+ }
+
+ if (self.target)
+ {
+ self.target2 = self.target;
+ self.goalentity = find(world, targetname, self.target);
+ }
+
+ if(autocvar_g_monsters_healthbars)
+ {
+ WaypointSprite_Spawn(self.netname, 0, 600, self, '0 0 1' * self.sprite_height, world, 0, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? TeamColor(self.team) : '1 0 0'));
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
+
+ MUTATOR_CALLHOOK(MonsterSpawn);
+}
+
+float monster_initialize(string net_name,
+ string bodymodel,
+ vector min_s,
+ vector max_s,
+ float nodrop,
+ void() dieproc,
+ void() spawnproc)
+{
+ if not(autocvar_g_monsters)
+ return FALSE;
+
+ // support for quake style removing monsters based on skill
+ if(autocvar_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; }
+ else if(autocvar_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; }
+ else if(autocvar_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; }
+ else if(autocvar_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; }
+ else if(autocvar_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; }
+
+ if(self.model == "")
+ if(bodymodel == "")
+ error("monsters: missing bodymodel!");
+
+ if(self.netname == "")
+ {
+ if(net_name != "" && self.realowner.classname == STR_PLAYER)
+ net_name = strzone(strdecolorize(sprintf("%s's %s", self.realowner.netname, net_name)));
+ self.netname = ((net_name == "") ? self.classname : net_name);
+ }
+
+ if(self.spawnflags & MONSTERFLAG_GIANT && !autocvar_g_monsters_nogiants)
+ ScaleMonster(5);
+ else if(!self.scale)
+ ScaleMonster(1);
+ else
+ ScaleMonster(self.scale);
+
+ Monster_CheckMinibossFlag();
+
+ min_s *= self.scale;
+ max_s *= self.scale;
+
+ if(self.team && !teamplay)
+ self.team = 0;
+
+ self.flags = FL_MONSTER;
+
+ if(self.model != "")
+ bodymodel = self.model;
+
+ if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
+ if not(self.monster_respawned)
+ monsters_total += 1;
+
+ precache_model(bodymodel);
+
+ setmodel(self, bodymodel);
+
+ setsize(self, min_s, max_s);
+
+ self.takedamage = DAMAGE_AIM;
+ self.bot_attack = TRUE;
+ self.iscreature = TRUE;
+ self.teleportable = TRUE;
+ self.damagedbycontents = TRUE;
+ self.damageforcescale = 0.003;
+ self.monster_die = dieproc;
+ self.event_damage = monsters_damage;
+ self.touch = MonsterTouch;
+ self.use = monster_use;
+ self.solid = SOLID_BBOX;
+ self.movetype = MOVETYPE_WALK;
+ self.delay = -1; // used in attack delay code
+ monsters_spawned += 1;
+ self.think = spawnproc;
+ self.nextthink = time;
+ self.enemy = world;
+ self.velocity = '0 0 0';
+ self.moveto = self.origin;
+ self.pos1 = self.origin;
+ self.pos2 = self.angles;
+
+ if not(self.monster_moveflags)
+ self.monster_moveflags = MONSTER_MOVE_WANDER;
+
+ if(autocvar_g_nodepthtestplayers)
+ self.effects |= EF_NODEPTHTEST;
+
+ if(autocvar_g_fullbrightplayers)
+ self.effects |= EF_FULLBRIGHT;
+
+ if not(nodrop)
+ {
+ setorigin(self, self.origin);
+ tracebox(self.origin + '0 0 100', min_s, max_s, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
+ setorigin(self, trace_endpos);
+ }
+
+ return TRUE;
+}
--- /dev/null
+float spawnmonster_checkinlist(string monster, string list)
+{
+ string l = strcat(" ", list, " ");
+
+ if(strstrofs(l, strcat(" ", monster, " "), 0) >= 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+entity spawnmonster (string monster, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+{
+ if not(autocvar_g_monsters)
+ {
+ if(spawnedby.flags & FL_CLIENT)
+ sprint(spawnedby, "Monsters are disabled. Enable g_monsters to spawn monsters\n");
+ return world;
+ }
+
+ if(spawnedby.vehicle) // no vehicle player spawning...
+ return world;
+
+ if(!spawncode_first_load)
+ {
+ initialize_field_db();
+ spawncode_first_load = TRUE;
+ }
+
+ entity e = spawn();
+
+ e.spawnflags = MONSTERFLAG_SPAWNED;
+
+ if not(respwn)
+ e.spawnflags |= MONSTERFLAG_NORESPAWN;
+
+ setorigin(e, orig);
+
+ if not(spawnmonster_checkinlist(monster, monsterlist()))
+ monster = "knight";
+
+ e.realowner = spawnedby;
+
+ if(moveflag)
+ e.monster_moveflags = moveflag;
+
+ if (spawnedby.classname == "monster_swarm")
+ e.monster_owner = own;
+ else if(spawnedby.flags & FL_CLIENT)
+ {
+ if(teamplay && autocvar_g_monsters_teams)
+ e.team = spawnedby.team; // colors handled in spawn code
+
+ if not(teamplay)
+ e.colormap = spawnedby.colormap;
+
+ if(autocvar_g_monsters_owners)
+ e.monster_owner = own; // using owner makes the monster non-solid for its master
+
+ e.angles = spawnedby.angles;
+ }
+
+ if(autocvar_g_monsters_giants_only)
+ e.spawnflags |= MONSTERFLAG_GIANT;
+
+ monster = strcat("$ spawnfunc_monster_", monster);
+
+ target_spawn_edit_entity(e, monster, world, world, world, world, world);
+
+ return e;
+}
--- /dev/null
+// cvars
+float autocvar_g_monster_demon;
+float autocvar_g_monster_demon_health;
+float autocvar_g_monster_demon_attack_jump_damage;
+float autocvar_g_monster_demon_damage;
+float autocvar_g_monster_demon_speed_walk;
+float autocvar_g_monster_demon_speed_run;
+
+// size
+const vector DEMON_MIN = '-32 -32 -24';
+const vector DEMON_MAX = '32 32 24';
+
+// animation
+#define demon_anim_stand 0
+#define demon_anim_walk 1
+#define demon_anim_run 2
+#define demon_anim_leap 3
+#define demon_anim_pain 4
+#define demon_anim_death 5
+#define demon_anim_attack 6
+
+void demon_think ()
+{
+ self.think = demon_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_demon_speed_run, autocvar_g_monster_demon_speed_walk, 100, demon_anim_run, demon_anim_walk, demon_anim_stand);
+}
+
+void demon_attack_melee ()
+{
+ float bigdmg = autocvar_g_monster_demon_damage * self.scale;
+
+ self.frame = demon_anim_attack;
+ self.attack_finished_single = time + 1;
+
+ monster_melee(self.enemy, bigdmg * monster_skill, 120, DEATH_MONSTER_DEMON_MELEE);
+}
+
+void Demon_JumpTouch ()
+{
+ if (self.health <= 0)
+ return;
+
+ float bigdmg = autocvar_g_monster_demon_attack_jump_damage * self.scale;
+
+ if (monster_isvalidtarget(other, self, FALSE))
+ {
+ if (vlen(self.velocity) > 300)
+ {
+ Damage(other, self, self, bigdmg * monster_skill, DEATH_MONSTER_DEMON_JUMP, other.origin, normalize(other.origin - self.origin));
+ self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+ }
+ }
+
+ if(self.flags & FL_ONGROUND)
+ self.touch = MonsterTouch;
+}
+
+float demon_jump ()
+{
+ makevectors(self.angles);
+ if(monster_leap(demon_anim_leap, Demon_JumpTouch, v_forward * 700 + '0 0 300', 0.8))
+ return TRUE;
+
+ return FALSE;
+}
+
+void demon_die ()
+{
+ Monster_CheckDropCvars ("demon");
+
+ self.frame = demon_anim_death;
+ self.think = Monster_Fade;
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.movetype = MOVETYPE_TOSS;
+ self.enemy = world;
+ self.nextthink = time + 3;
+ self.pain_finished = self.nextthink;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void demon_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_demon_health * self.scale;
+
+ self.damageforcescale = 0;
+ self.classname = "monster_demon";
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = demon_attack_melee;
+ self.attack_ranged = demon_jump;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.frame = demon_anim_stand;
+ self.think = demon_think;
+ self.sprite_height = 30 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+/* QUAKED monster_demon (1 0 0) (-32 -32 -24) (32 32 64) Ambush */
+void spawnfunc_monster_demon ()
+{
+ if not(autocvar_g_monster_demon)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_demon;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Fiend",
+ "models/monsters/demon.mdl",
+ DEMON_MIN, DEMON_MAX,
+ FALSE,
+ demon_die, demon_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
+
+// Compatibility with old spawns
+void spawnfunc_monster_demon1 () { spawnfunc_monster_demon(); }
--- /dev/null
+// size
+const vector DOG_MAX = '16 16 12';
+const vector DOG_MIN = '-16 -16 -24';
+
+// cvars
+float autocvar_g_monster_dog;
+float autocvar_g_monster_dog_health;
+float autocvar_g_monster_dog_bite_damage;
+float autocvar_g_monster_dog_attack_jump_damage;
+float autocvar_g_monster_dog_speed_walk;
+float autocvar_g_monster_dog_speed_run;
+
+// animations
+#define dog_anim_idle 0
+#define dog_anim_walk 1
+#define dog_anim_run 2
+#define dog_anim_attack 3
+#define dog_anim_die 4
+#define dog_anim_pain 5
+
+void Dog_JumpTouch ()
+{
+ float bigdmg = autocvar_g_monster_dog_attack_jump_damage * self.scale;
+ if (self.health <= 0)
+ return;
+
+ if (other.takedamage)
+ {
+ if (vlen(self.velocity) > 300)
+ Damage(self.enemy, self, self, bigdmg * monster_skill, DEATH_MONSTER_DOG_JUMP, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+ }
+
+ if(self.flags & FL_ONGROUND)
+ self.touch = MonsterTouch;
+}
+
+void dog_think ()
+{
+ self.think = dog_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_dog_speed_run, autocvar_g_monster_dog_speed_walk, 50, dog_anim_run, dog_anim_walk, dog_anim_idle);
+}
+
+void dog_attack ()
+{
+ float bigdmg = autocvar_g_monster_dog_bite_damage * self.scale;
+
+ self.frame = dog_anim_attack;
+ self.attack_finished_single = time + 0.7;
+
+ monster_melee(self.enemy, bigdmg * monster_skill, 100, DEATH_MONSTER_DOG_BITE);
+}
+
+float dog_jump ()
+{
+ makevectors(self.angles);
+ if(monster_leap(dog_anim_attack, Dog_JumpTouch, v_forward * 300 + '0 0 200', 0.8))
+ return TRUE;
+
+ return FALSE;
+}
+
+void dog_die ()
+{
+ Monster_CheckDropCvars ("dog");
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.nextthink = time + 2.1;
+ self.think = Monster_Fade;
+ self.pain_finished = self.nextthink;
+ self.movetype = MOVETYPE_TOSS;
+ self.frame = dog_anim_die;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void dog_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_dog_health * self.scale;
+
+ self.damageforcescale = 0;
+ self.classname = "monster_dog";
+ self.attack_melee = dog_attack;
+ self.attack_ranged = dog_jump;
+ self.checkattack = GenericCheckAttack;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = dog_think;
+ self.frame = dog_anim_idle;
+ self.sprite_height = 20 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_dog ()
+{
+ if not(autocvar_g_monster_dog)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_dog;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ if not (monster_initialize(
+ "Cerberus",
+ "models/monsters/dog.dpm",
+ DOG_MIN, DOG_MAX,
+ FALSE,
+ dog_die, dog_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
--- /dev/null
+// size
+const vector ENFORCER_MIN = '-16 -16 -24';
+const vector ENFORCER_MAX = '16 16 24';
+
+// cvars
+float autocvar_g_monster_enforcer;
+float autocvar_g_monster_enforcer_health;
+float autocvar_g_monster_enforcer_speed_walk;
+float autocvar_g_monster_enforcer_speed_run;
+float autocvar_g_monster_enforcer_attack_uzi_bullets;
+
+// animations
+#define enforcer_anim_stand 0
+#define enforcer_anim_walk 1
+#define enforcer_anim_run 2
+#define enforcer_anim_attack 3
+#define enforcer_anim_death1 4
+#define enforcer_anim_death2 5
+#define enforcer_anim_pain1 6
+#define enforcer_anim_pain2 7
+#define enforcer_anim_pain3 8
+#define enforcer_anim_pain4 9
+
+void enforcer_think ()
+{
+ self.think = enforcer_think;
+ self.nextthink = time + 0.1;
+
+ if(self.delay != -1)
+ self.nextthink = self.delay;
+
+ if(time < self.attack_finished_single)
+ monster_move(0, 0, 0, enforcer_anim_attack, enforcer_anim_attack, enforcer_anim_attack);
+ else
+ monster_move(autocvar_g_monster_enforcer_speed_run, autocvar_g_monster_enforcer_speed_walk, 100, enforcer_anim_run, enforcer_anim_walk, enforcer_anim_stand);
+}
+
+void enforcer_laser ()
+{
+ self.frame = enforcer_anim_attack;
+ self.attack_finished_single = time + 0.8;
+ W_Laser_Attack(0);
+}
+
+float enf_missile_laser ()
+{
+ enforcer_laser();
+ return TRUE;
+}
+
+void enforcer_shotgun ()
+{
+ self.frame = enforcer_anim_attack;
+ self.attack_finished_single = time + 0.8;
+ W_Shotgun_Attack();
+}
+
+float enf_missile_shotgun ()
+{
+ enforcer_shotgun();
+ return TRUE;
+}
+
+.float enf_cycles;
+void enforcer_uzi_fire ()
+{
+ self.enf_cycles += 1;
+
+ if(self.enf_cycles > autocvar_g_monster_enforcer_attack_uzi_bullets)
+ {
+ self.monster_delayedattack = func_null;
+ self.delay = -1;
+ return;
+ }
+ W_UZI_Attack(DEATH_MONSTER_ENFORCER_NAIL);
+ self.delay = time + 0.1;
+ self.monster_delayedattack = enforcer_uzi_fire;
+}
+
+void enforcer_uzi ()
+{
+ self.frame = enforcer_anim_attack;
+ self.attack_finished_single = time + 0.8;
+ self.delay = time + 0.1;
+ self.monster_delayedattack = enforcer_uzi_fire;
+}
+
+float enf_missile_uzi ()
+{
+ self.enf_cycles = 0;
+ enforcer_uzi();
+ return TRUE;
+}
+
+void enforcer_rl ()
+{
+ self.frame = enforcer_anim_attack;
+ self.attack_finished_single = time + 0.8;
+ W_Rocket_Attack();
+}
+
+float enf_missile_rocket ()
+{
+ enforcer_rl();
+ return TRUE;
+}
+
+void enforcer_electro ()
+{
+ self.frame = enforcer_anim_attack;
+ self.attack_finished_single = time + 0.8;
+ W_Electro_Attack();
+}
+
+float enf_missile_plasma ()
+{
+ enforcer_electro();
+ return TRUE;
+}
+
+void enforcer_die ()
+{
+ Monster_CheckDropCvars ("enforcer");
+
+ self.solid = SOLID_NOT;
+ self.movetype = MOVETYPE_TOSS;
+ self.think = Monster_Fade;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+
+ if (self.attack_ranged == enf_missile_rocket)
+ W_ThrowNewWeapon(self, WEP_ROCKET_LAUNCHER, 0, self.origin, self.velocity);
+ else if (self.attack_ranged == enf_missile_plasma)
+ W_ThrowNewWeapon(self, WEP_ELECTRO, 0, self.origin, self.velocity);
+ else if (self.attack_ranged == enf_missile_shotgun)
+ W_ThrowNewWeapon(self, WEP_SHOTGUN, 0, self.origin, self.velocity);
+ else if (self.attack_ranged == enf_missile_uzi)
+ W_ThrowNewWeapon(self, WEP_UZI, 0, self.origin, self.velocity);
+ else
+ W_ThrowNewWeapon(self, WEP_LASER, 0, self.origin, self.velocity);
+
+ if (random() > 0.5)
+ self.frame = enforcer_anim_death1;
+ else
+ self.frame = enforcer_anim_death2;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void enforcer_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_enforcer_health * self.scale;
+
+ self.damageforcescale = 0;
+ self.classname = "monster_enforcer";
+ self.checkattack = GenericCheckAttack;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = enforcer_think;
+ self.items = (IT_SHELLS | IT_ROCKETS | IT_NAILS | IT_CELLS);
+ self.sprite_height = 30 * self.scale;
+
+ local float r = random();
+ if (r < 0.20)
+ self.attack_ranged = enf_missile_rocket;
+ else if (r < 0.40)
+ self.attack_ranged = enf_missile_plasma;
+ else if (r < 0.60)
+ self.attack_ranged = enf_missile_shotgun;
+ else if (r < 0.80)
+ self.attack_ranged = enf_missile_uzi;
+ else
+ self.attack_ranged = enf_missile_laser;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_enforcer ()
+{
+ if not(autocvar_g_monster_enforcer)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_enforcer;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Enforcer",
+ "models/monsters/enforcer.mdl",
+ ENFORCER_MIN, ENFORCER_MAX,
+ FALSE,
+ enforcer_die, enforcer_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
--- /dev/null
+// size
+const vector FISH_MIN = '-16 -16 -24';
+const vector FISH_MAX = '16 16 16';
+
+// cvars
+float autocvar_g_monster_fish;
+float autocvar_g_monster_fish_health;
+float autocvar_g_monster_fish_damage;
+float autocvar_g_monster_fish_speed_walk;
+float autocvar_g_monster_fish_speed_run;
+
+// animations
+#define fish_anim_attack 0
+#define fish_anim_death 1
+#define fish_anim_swim 2
+#define fish_anim_pain 3
+
+void fish_think ()
+{
+ self.think = fish_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_fish_speed_run, autocvar_g_monster_fish_speed_walk, 10, fish_anim_swim, fish_anim_swim, fish_anim_swim);
+}
+
+void fish_attack ()
+{
+ float bigdmg = autocvar_g_monster_fish_damage * self.scale;
+
+ self.frame = fish_anim_attack;
+ self.attack_finished_single = time + 0.5;
+
+ monster_melee(self.enemy, bigdmg * monster_skill, 60, DEATH_MONSTER_FISH_BITE);
+}
+
+void fish_die ()
+{
+ Monster_CheckDropCvars ("fish");
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.pain_finished = self.nextthink;
+ self.frame = fish_anim_death;
+ self.think = Monster_Fade;
+ self.nextthink = time + 2.1;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void fish_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_fish_health * self.scale;
+
+ self.damageforcescale = 0.5;
+ self.classname = "monster_fish";
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = fish_attack;
+ self.flags |= FL_SWIM;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = fish_think;
+ self.sprite_height = 20 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_fish ()
+{
+ if not(autocvar_g_monster_fish)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_fish;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Rotfish",
+ "models/monsters/fish.mdl",
+ FISH_MIN, FISH_MAX,
+ TRUE,
+ fish_die, fish_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
--- /dev/null
+// size
+const vector HELLKNIGHT_MIN = '-16 -16 -24';
+const vector HELLKNIGHT_MAX = '16 16 32';
+
+// cvars
+float autocvar_g_monster_hellknight;
+float autocvar_g_monster_hellknight_health;
+float autocvar_g_monster_hellknight_melee_damage;
+float autocvar_g_monster_hellknight_inferno_damage;
+float autocvar_g_monster_hellknight_inferno_damagetime;
+float autocvar_g_monster_hellknight_inferno_chance;
+float autocvar_g_monster_hellknight_speed_walk;
+float autocvar_g_monster_hellknight_speed_run;
+float autocvar_g_monster_hellknight_fireball_damage;
+float autocvar_g_monster_hellknight_fireball_force;
+float autocvar_g_monster_hellknight_fireball_radius;
+float autocvar_g_monster_hellknight_fireball_chance;
+float autocvar_g_monster_hellknight_fireball_edgedamage;
+float autocvar_g_monster_hellknight_spike_chance;
+float autocvar_g_monster_hellknight_spike_force;
+float autocvar_g_monster_hellknight_spike_radius;
+float autocvar_g_monster_hellknight_spike_edgedamage;
+float autocvar_g_monster_hellknight_spike_damage;
+float autocvar_g_monster_hellknight_jump_chance;
+float autocvar_g_monster_hellknight_jump_damage;
+float autocvar_g_monster_hellknight_jump_dist;
+
+// animations
+#define hellknight_anim_stand 0
+#define hellknight_anim_walk 1
+#define hellknight_anim_run 2
+#define hellknight_anim_pain 3
+#define hellknight_anim_death1 4
+#define hellknight_anim_death2 5
+#define hellknight_anim_charge1 6
+#define hellknight_anim_magic1 7
+#define hellknight_anim_magic2 8
+#define hellknight_anim_charge2 9
+#define hellknight_anim_slice 10
+#define hellknight_anim_smash 11
+#define hellknight_anim_wattack 12
+#define hellknight_anim_magic3 13
+
+void hknight_spike_think()
+{
+ if(self)
+ {
+ RadiusDamage (self, self.realowner, autocvar_g_monster_hellknight_spike_damage * self.realowner.scale, autocvar_g_monster_hellknight_spike_edgedamage, autocvar_g_monster_hellknight_spike_force, world, autocvar_g_monster_hellknight_spike_radius, WEP_CRYLINK, other);
+ remove(self);
+ }
+}
+
+void hknight_spike_touch()
+{
+ PROJECTILE_TOUCH;
+
+ pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
+
+ hknight_spike_think();
+}
+
+void() hellknight_think;
+void hknight_shoot ()
+{
+ local entity missile = world;
+ local vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+ local float dist = vlen (self.enemy.origin - self.origin), flytime = 0;
+
+ flytime = dist * 0.002;
+ if (flytime < 0.1)
+ flytime = 0.1;
+
+ self.effects |= EF_MUZZLEFLASH;
+ sound (self, CHAN_WEAPON, "weapons/spike.wav", 1, ATTN_NORM);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ missile.solid = SOLID_TRIGGER;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ setsize (missile, '0 0 0', '0 0 0');
+ setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
+ missile.scale = self.scale;
+ missile.velocity = dir * 400;
+ missile.avelocity = '300 300 300';
+ missile.nextthink = time + 5;
+ missile.think = hknight_spike_think;
+ missile.enemy = self.enemy;
+ missile.touch = hknight_spike_touch;
+ CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void hknight_inferno ()
+{
+ traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
+ if (trace_fraction != 1)
+ return; // not visible
+ if(enemy_range() <= 2000)
+ Fire_AddDamage(self.enemy, self, autocvar_g_monster_hellknight_inferno_damage * monster_skill, autocvar_g_monster_hellknight_inferno_damagetime, self.projectiledeathtype);
+}
+
+void hknight_infernowarning ()
+{
+ if(!self.enemy)
+ return;
+
+ traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
+ if (trace_fraction != 1)
+ return; // not visible
+ self.enemy.effects |= EF_MUZZLEFLASH;
+ sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
+
+ hknight_inferno();
+}
+
+float() hknight_magic;
+float hknight_checkmagic ()
+{
+ local vector v1 = '0 0 0', v2 = '0 0 0';
+ local float dot = 0;
+
+ // use magic to kill zombies as they heal too fast for sword
+ if (self.enemy.classname == "monster_zombie")
+ {
+ traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, FALSE, self);
+ if (trace_ent == self.enemy)
+ {
+ hknight_magic();
+ return TRUE;
+ }
+ }
+
+ if (random() < 0.25)
+ return FALSE; // 25% of the time it won't do anything
+ v1 = normalize(self.enemy.velocity);
+ v2 = normalize(self.enemy.origin - self.origin);
+ dot = v1 * v2;
+ if (dot >= 0.7) // moving away
+ if (vlen(self.enemy.velocity) >= 150) // walking/running away
+ return hknight_magic();
+ return FALSE;
+}
+
+void() hellknight_charge;
+void CheckForCharge ()
+{
+ // check for mad charge
+ if (time < self.attack_finished_single)
+ return;
+ if (fabs(self.origin_z - self.enemy.origin_z) > 20)
+ return; // too much height change
+ if (vlen (self.origin - self.enemy.origin) < 80)
+ return; // use regular attack
+ if (hknight_checkmagic())
+ return; // chose magic
+
+ // charge
+ hellknight_charge();
+}
+
+void CheckContinueCharge ()
+{
+ if(hknight_checkmagic())
+ return; // chose magic
+ if(time >= self.attack_finished_single)
+ {
+ hellknight_think();
+ return; // done charging
+ }
+}
+
+void hellknight_think ()
+{
+ self.think = hellknight_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_hellknight_speed_run, autocvar_g_monster_hellknight_speed_walk, 100, hellknight_anim_run, hellknight_anim_walk, hellknight_anim_stand);
+}
+
+.float hknight_cycles;
+void hellknight_magic ()
+{
+ self.hknight_cycles += 1;
+ self.think = hellknight_magic;
+
+ if(self.hknight_cycles >= 5)
+ {
+ self.frame = hellknight_anim_magic1;
+ self.attack_finished_single = time + 0.7;
+ hknight_infernowarning();
+ self.think = hellknight_think;
+ }
+
+ self.nextthink = time + 0.1;
+}
+
+void hknight_fireball_explode(entity targ)
+{
+ float scle = self.realowner.scale;
+ if(self)
+ {
+ RadiusDamage (self, self.realowner, autocvar_g_monster_hellknight_fireball_damage * scle, autocvar_g_monster_hellknight_fireball_edgedamage * scle, autocvar_g_monster_hellknight_fireball_force * scle, world, autocvar_g_monster_hellknight_fireball_radius * scle, WEP_FIREBALL, targ);
+ if(targ)
+ Fire_AddDamage(targ, self, 5 * monster_skill, autocvar_g_monster_hellknight_inferno_damagetime, self.projectiledeathtype);
+ remove(self);
+ }
+}
+
+void hknight_fireball_think()
+{
+ hknight_fireball_explode(world);
+}
+
+void hknight_fireball_touch()
+{
+ PROJECTILE_TOUCH;
+
+ hknight_fireball_explode(other);
+}
+
+void hellknight_fireball ()
+{
+ local entity missile = spawn();
+ local vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+ vector fmins = ((self.scale >= 2) ? '-16 -16 -16' : '-4 -4 -4'), fmaxs = ((self.scale >= 2) ? '16 16 16' : '4 4 4');
+
+ self.effects |= EF_MUZZLEFLASH;
+ sound (self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
+
+ missile.owner = missile.realowner = self;
+ missile.solid = SOLID_TRIGGER;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ setsize (missile, fmins, fmaxs);
+ setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
+ missile.velocity = dir * 400;
+ missile.avelocity = '300 300 300';
+ missile.nextthink = time + 5;
+ missile.think = hknight_fireball_think;
+ missile.enemy = self.enemy;
+ missile.touch = hknight_fireball_touch;
+ CSQCProjectile(missile, TRUE, ((self.scale >= 2) ? PROJECTILE_FIREBALL : PROJECTILE_FIREMINE), TRUE);
+
+ self.delay = -1;
+}
+
+void hellknight_magic2 ()
+{
+ self.frame = hellknight_anim_magic2;
+ self.attack_finished_single = time + 1.2;
+ self.delay = time + 0.4;
+ self.monster_delayedattack = hellknight_fireball;
+}
+
+void hellknight_spikes ()
+{
+ self.think = hellknight_spikes;
+ self.nextthink = time + 0.1;
+ self.hknight_cycles += 1;
+ hknight_shoot();
+ if(self.hknight_cycles >= 7)
+ self.think = hellknight_think;
+}
+
+void hellknight_magic3 ()
+{
+ self.frame = hellknight_anim_magic3;
+ self.attack_finished_single = time + 1;
+ self.think = hellknight_spikes;
+ self.nextthink = time + 0.4;
+}
+
+void hellknight_charge ()
+{
+ self.frame = hellknight_anim_charge1;
+ self.attack_finished_single = time + 0.5;
+
+ hknight_checkmagic();
+ monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 70, DEATH_MONSTER_MELEE);
+ hknight_checkmagic();
+}
+
+void hellknight_charge2 ()
+{
+ self.frame = hellknight_anim_charge2;
+ self.attack_finished_single = time + 0.5;
+
+ CheckContinueCharge ();
+ monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 70, DEATH_MONSTER_MELEE);
+}
+
+void hellknight_slice ()
+{
+ self.frame = hellknight_anim_slice;
+ self.attack_finished_single = time + 0.7;
+ monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 70, DEATH_MONSTER_MELEE);
+}
+
+void hellknight_smash ()
+{
+ self.frame = hellknight_anim_smash;
+ self.attack_finished_single = time + 0.7;
+ monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 70, DEATH_MONSTER_MELEE);
+}
+
+void hellknight_weapon_attack ()
+{
+ self.frame = hellknight_anim_wattack;
+ self.attack_finished_single = time + 0.7;
+ monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 70, DEATH_MONSTER_MELEE);
+}
+
+float hknight_type;
+void hknight_melee ()
+{
+ hknight_type += 1;
+
+ if (hknight_type == 1)
+ hellknight_slice();
+ else if (hknight_type == 2)
+ hellknight_smash();
+ else
+ {
+ hellknight_weapon_attack();
+ hknight_type = 0;
+ }
+}
+
+float hknight_magic ()
+{
+ if not(self.flags & FL_ONGROUND)
+ return FALSE;
+
+ if not(self.enemy)
+ return FALSE; // calling attack check with no enemy?!
+
+ if(time < self.attack_finished_single)
+ return FALSE;
+
+ self.hknight_cycles = 0;
+
+ if (self.enemy.classname == "monster_zombie")
+ {
+ // always use fireball to kill zombies
+ hellknight_magic2();
+ self.attack_finished_single = time + 2;
+ return TRUE;
+ }
+ RandomSelection_Init();
+ RandomSelection_Add(world, 0, "fireball", autocvar_g_monster_hellknight_fireball_chance, 1);
+ RandomSelection_Add(world, 0, "inferno", autocvar_g_monster_hellknight_inferno_chance, 1);
+ RandomSelection_Add(world, 0, "spikes", autocvar_g_monster_hellknight_spike_chance, 1);
+ if(self.health >= 100)
+ RandomSelection_Add(world, 0, "jump", ((enemy_range() > autocvar_g_monster_hellknight_jump_dist * self.scale) ? 1 : autocvar_g_monster_hellknight_jump_chance), 1);
+
+ switch(RandomSelection_chosen_string)
+ {
+ case "fireball":
+ {
+ hellknight_magic2();
+ self.attack_finished_single = time + 2;
+ return TRUE;
+ }
+ case "spikes":
+ {
+ hellknight_magic3();
+ self.attack_finished_single = time + 3;
+ return TRUE;
+ }
+ case "inferno":
+ {
+ hellknight_magic();
+ self.attack_finished_single = time + 3;
+ return TRUE;
+ }
+ case "jump":
+ {
+ if (enemy_range() >= 400)
+ if (findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self))
+ {
+ self.velocity = findtrajectory_velocity;
+ Damage(self.enemy, self, self, autocvar_g_monster_hellknight_jump_damage * monster_skill, DEATH_VHCRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+ self.attack_finished_single = time + 2;
+ return TRUE;
+ }
+ return FALSE;
+ }
+ default:
+ return FALSE;
+ }
+ // never get here
+}
+
+void hellknight_die ()
+{
+ float chance = random();
+ Monster_CheckDropCvars ("hellknight");
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.think = Monster_Fade;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+
+ if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS)
+ {
+ self.superweapons_finished = time + autocvar_g_balance_superweapons_time;
+ W_ThrowNewWeapon(self, WEP_FIREBALL, 0, self.origin, self.velocity);
+ }
+
+ if (random() > 0.5)
+ self.frame = hellknight_anim_death1;
+ else
+ self.frame = hellknight_anim_death2;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void hellknight_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_hellknight_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_hellknight";
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = hknight_melee;
+ self.attack_ranged = hknight_magic;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = hellknight_think;
+ self.sprite_height = 30 * self.scale;
+ self.frame = hellknight_anim_stand;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_hell_knight ()
+{
+ if not(autocvar_g_monster_hellknight)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_hell_knight;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Hell-knight",
+ "models/monsters/hknight.mdl",
+ HELLKNIGHT_MIN, HELLKNIGHT_MAX,
+ FALSE,
+ hellknight_die, hellknight_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ precache_sound ("weapons/spike.wav");
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_hellknight () { spawnfunc_monster_hell_knight(); }
--- /dev/null
+// size
+const vector KNIGHT_MIN = '-16 -16 -24';
+const vector KNIGHT_MAX = '16 16 32';
+
+// cvars
+float autocvar_g_monster_knight;
+float autocvar_g_monster_knight_health;
+float autocvar_g_monster_knight_melee_damage;
+float autocvar_g_monster_knight_speed_walk;
+float autocvar_g_monster_knight_speed_run;
+
+// animations
+#define knight_anim_stand 0
+#define knight_anim_run 1
+#define knight_anim_runattack 2
+#define knight_anim_pain1 3
+#define knight_anim_pain2 4
+#define knight_anim_attack 5
+#define knight_anim_walk 6
+#define knight_anim_kneel 7
+#define knight_anim_standing 8
+#define knight_anim_death1 9
+#define knight_anim_death2 10
+
+void knight_think ()
+{
+ self.think = knight_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 50, knight_anim_run, knight_anim_walk, knight_anim_stand);
+}
+
+void knight_attack ()
+{
+ local float len = vlen(self.velocity);
+
+ self.frame = ((len < 50) ? knight_anim_attack : knight_anim_runattack);
+
+ self.attack_finished_single = time + 0.9;
+
+ monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 80, DEATH_MONSTER_MELEE);
+}
+
+void knight_die ()
+{
+ Monster_CheckDropCvars ("knight");
+
+ self.frame = ((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.think = Monster_Fade;
+ self.movetype = MOVETYPE_TOSS;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void knight_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_knight_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_knight";
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = knight_attack;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = knight_think;
+ self.sprite_height = 30 * self.scale;
+ self.frame = knight_anim_stand;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_knight ()
+{
+ if not(autocvar_g_monster_knight)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_knight;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Knight",
+ "models/monsters/knight.mdl",
+ KNIGHT_MIN, KNIGHT_MAX,
+ FALSE,
+ knight_die, knight_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
--- /dev/null
+// size
+const vector OGRE_MIN = '-32 -32 -24';
+const vector OGRE_MAX = '32 32 32';
+
+// cvars
+float autocvar_g_monster_ogre;
+float autocvar_g_monster_ogre_health;
+float autocvar_g_monster_ogre_chainsaw_damage;
+float autocvar_g_monster_ogre_speed_walk;
+float autocvar_g_monster_ogre_speed_run;
+float autocvar_g_monster_ogre_attack_uzi_bullets;
+
+// animations
+#define ogre_anim_stand 0
+#define ogre_anim_walk 1
+#define ogre_anim_run 2
+#define ogre_anim_swing 3
+#define ogre_anim_smash 4
+#define ogre_anim_shoot 5
+#define ogre_anim_pain1 6
+#define ogre_anim_pain2 7
+#define ogre_anim_pain3 8
+#define ogre_anim_pain4 9
+#define ogre_anim_pain5 10
+#define ogre_anim_death1 11
+#define ogre_anim_death2 12
+#define ogre_anim_pull 13
+
+void chainsaw (float side)
+{
+ if (!self.enemy)
+ return;
+
+ if (enemy_range() > 100 * self.scale)
+ return;
+
+ Damage(self.enemy, self, self, autocvar_g_monster_ogre_chainsaw_damage * monster_skill, DEATH_MONSTER_OGRE_CHAINSAW, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+}
+
+void ogre_think ()
+{
+ self.think = ogre_think;
+ self.nextthink = time + 0.1;
+
+ if(self.delay != -1)
+ self.nextthink = self.delay;
+
+ monster_move(autocvar_g_monster_ogre_speed_run, autocvar_g_monster_ogre_speed_walk, 300, ogre_anim_run, ogre_anim_walk, ogre_anim_stand);
+}
+
+.float ogre_cycles;
+void ogre_swing ()
+{
+ self.ogre_cycles += 1;
+ self.frame = ogre_anim_swing;
+ if(self.ogre_cycles == 1)
+ self.attack_finished_single = time + 1.3;
+ self.angles_y = self.angles_y + random()* 25;
+ self.nextthink = time + 0.2;
+ self.think = ogre_swing;
+
+ if(self.ogre_cycles <= 3)
+ chainsaw(200);
+ else if(self.ogre_cycles <= 8)
+ chainsaw(-200);
+ else
+ chainsaw(0);
+
+ if(self.ogre_cycles >= 10)
+ self.think = ogre_think;
+}
+
+void ogre_smash_2 ()
+{
+ chainsaw(0);
+}
+
+void ogre_smash ()
+{
+ self.frame = ogre_anim_smash;
+ self.attack_finished_single = time + 0.5;
+ chainsaw(0);
+ self.monster_delayedattack = ogre_smash_2;
+ self.delay = time + 0.1;
+}
+
+void ogre_uzi_fire ()
+{
+ self.ogre_cycles += 1;
+
+ if(self.ogre_cycles > autocvar_g_monster_ogre_attack_uzi_bullets)
+ {
+ self.monster_delayedattack = func_null;
+ self.delay = -1;
+ return;
+ }
+ W_UZI_Attack(DEATH_MONSTER_OGRE_NAIL);
+ self.delay = time + 0.1;
+ self.monster_delayedattack = ogre_uzi_fire;
+}
+
+void ogre_uzi ()
+{
+ self.frame = ogre_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+ self.delay = time + 0.1;
+ self.monster_delayedattack = ogre_uzi_fire;
+}
+
+void ogre_gl ()
+{
+ W_Grenade_Attack2();
+ self.frame = ogre_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+}
+
+float ogre_missile ()
+{
+ self.ogre_cycles = 0;
+ if (random() < 0.20)
+ {
+ ogre_uzi();
+ return TRUE;
+ }
+ else
+ {
+ ogre_gl();
+ return TRUE;
+ }
+}
+
+void ogre_melee ()
+{
+ self.ogre_cycles = 0;
+ if (random() > 0.5)
+ ogre_smash();
+ else
+ ogre_swing();
+}
+
+void ogre_die()
+{
+ Monster_CheckDropCvars ("ogre");
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+ self.movetype = MOVETYPE_TOSS;
+ self.think = Monster_Fade;
+
+ W_ThrowNewWeapon(self, WEP_GRENADE_LAUNCHER, 0, self.origin, self.velocity);
+ if (random() < 0.5)
+ self.frame = ogre_anim_death1;
+ else
+ self.frame = ogre_anim_death2;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void ogre_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_ogre_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_ogre";
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = ogre_melee;
+ self.frame = ogre_anim_pull;
+ self.attack_ranged = ogre_missile;
+ self.nextthink = time + 1;
+ self.think = ogre_think;
+ self.sprite_height = 40 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_ogre ()
+{
+ if not(autocvar_g_monster_ogre)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_ogre;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Ogre",
+ "models/monsters/ogre.mdl",
+ OGRE_MIN, OGRE_MAX,
+ FALSE,
+ ogre_die, ogre_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ weapon_action(WEP_GRENADE_LAUNCHER, WR_PRECACHE);
+}
--- /dev/null
+// size
+const vector SHALRATH_MIN = '-32 -32 -24';
+const vector SHALRATH_MAX = '32 32 32';
+
+// cvars
+float autocvar_g_monster_shalrath;
+float autocvar_g_monster_shalrath_health;
+float autocvar_g_monster_shalrath_damage;
+float autocvar_g_monster_shalrath_speed;
+
+// animations
+#define shalrath_anim_attack 0
+#define shalrath_anim_pain 1
+#define shalrath_anim_death 2
+#define shalrath_anim_walk 3
+
+void() ShalMissile;
+
+void shalrath_think ()
+{
+ self.think = shalrath_think;
+ self.nextthink = time + 0.1;
+
+ if(self.delay != -1)
+ self.nextthink = self.delay;
+
+ monster_move(autocvar_g_monster_shalrath_speed, autocvar_g_monster_shalrath_speed, 50, shalrath_anim_walk, shalrath_anim_walk, shalrath_anim_walk);
+}
+
+void shalrath_attack ()
+{
+ self.frame = shalrath_anim_attack;
+ self.delay = time + 0.1;
+ self.attack_finished_single = time + 0.7;
+ self.monster_delayedattack = ShalMissile;
+}
+
+void shalrathattack_melee ()
+{
+ float bigdmg = 0, rdmg = autocvar_g_monster_shalrath_damage * random();
+
+ bigdmg = rdmg * self.scale;
+
+ monster_melee(self.enemy, bigdmg * monster_skill, 120, DEATH_MONSTER_SHALRATH_MELEE);
+}
+
+void shalrath_attack_melee ()
+{
+ self.monster_delayedattack = shalrathattack_melee;
+ self.delay = time + 0.2;
+ self.frame = shalrath_anim_attack;
+ self.attack_finished_single = time + 0.7;
+}
+
+float shal_missile ()
+{
+ // don't throw if it is blocked
+ traceline(self.origin + '0 0 10', self.enemy.origin + '0 0 10', FALSE, self);
+ if (enemy_range() > 1000)
+ return FALSE;
+ if (trace_ent != self.enemy)
+ return FALSE;
+ shalrath_attack();
+ return TRUE;
+}
+
+void() ShalHome;
+void ShalMissile_Spawn ()
+{
+ local vector dir = '0 0 0';
+ local float dist = 0;
+
+ self.realowner.effects |= EF_MUZZLEFLASH;
+
+ dir = normalize((self.owner.enemy.origin + '0 0 10') - self.owner.origin);
+ dist = vlen (self.owner.enemy.origin - self.owner.origin);
+
+ self.solid = SOLID_BBOX;
+ self.movetype = MOVETYPE_FLYMISSILE;
+ CSQCProjectile(self, TRUE, PROJECTILE_CRYLINK, TRUE);
+
+ self.realowner.v_angle = self.realowner.angles;
+ makevectors (self.realowner.angles);
+
+ setsize (self, '0 0 0', '0 0 0');
+
+ setorigin (self, self.realowner.origin + v_forward * 14 + '0 0 30' + v_right * -14);
+ self.velocity = dir * 400;
+ self.avelocity = '300 300 300';
+ self.enemy = self.realowner.enemy;
+ self.touch = W_Plasma_TouchExplode;
+ ShalHome();
+}
+
+void ShalMissile ()
+{
+ local entity missile = world;
+
+ sound (self, CHAN_WEAPON, "weapons/spike.wav", 1, ATTN_NORM);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+
+ missile.think = ShalMissile_Spawn;
+ missile.nextthink = time;
+}
+
+.float shal_cycles;
+void ShalHome ()
+{
+ local vector dir = '0 0 0', vtemp = self.enemy.origin + '0 0 10';
+
+ self.shal_cycles += 1;
+ if (self.enemy.health <= 0 || self.owner.health <= 0 || self.shal_cycles >= 20)
+ {
+ remove(self);
+ return;
+ }
+ dir = normalize(vtemp - self.origin);
+ UpdateCSQCProjectile(self);
+ if (autocvar_skill == 3)
+ self.velocity = dir * 350;
+ else
+ self.velocity = dir * 250;
+ self.nextthink = time + 0.2;
+ self.think = ShalHome;
+}
+
+float ShalrathCheckAttack ()
+{
+ local vector spot1 = '0 0 0', spot2 = '0 0 0';
+ local entity targ = self.enemy;
+
+ if (self.health <= 0 || targ == world || targ.health < 1)
+ return FALSE;
+
+ if(self.monster_delayedattack && self.delay != -1)
+ {
+ if(time < self.delay)
+ return FALSE;
+
+ self.monster_delayedattack();
+ self.delay = -1;
+ self.monster_delayedattack = func_null;
+ }
+
+ if(time < self.attack_finished_single)
+ return FALSE;
+
+ if (vlen(self.enemy.origin - self.origin) <= 120)
+ { // melee attack
+ if (self.attack_melee)
+ {
+ self.attack_melee();
+ return TRUE;
+ }
+ }
+
+ if (vlen(targ.origin - self.origin) >= 2000) // long traces are slow
+ return FALSE;
+
+// see if any entities are in the way of the shot
+ spot1 = self.origin + '0 0 10';
+ spot2 = targ.origin + '0 0 10';
+
+ traceline (spot1, spot2, FALSE, self);
+
+ if (trace_ent != targ && trace_fraction < 1)
+ return FALSE; // don't have a clear shot
+
+ //if (trace_inopen && trace_inwater)
+ // return FALSE; // sight line crossed contents
+
+ if (random() < 0.2)
+ if (self.attack_ranged())
+ return TRUE;
+
+ return FALSE;
+}
+
+void shalrath_die ()
+{
+ Monster_CheckDropCvars ("shalrath");
+
+ self.think = Monster_Fade;
+ self.frame = shalrath_anim_death;
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+ self.movetype = MOVETYPE_TOSS;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void shalrath_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_shalrath_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_shalrath";
+ self.checkattack = ShalrathCheckAttack;
+ self.attack_ranged = shal_missile;
+ self.attack_melee = shalrath_attack_melee;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = shalrath_think;
+ self.frame = shalrath_anim_walk;
+ self.sprite_height = 40 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_shalrath ()
+{
+ if not(autocvar_g_monster_shalrath)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_shalrath;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Vore",
+ "models/monsters/shalrath.mdl",
+ SHALRATH_MIN, SHALRATH_MAX,
+ FALSE,
+ shalrath_die, shalrath_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_vore () { spawnfunc_monster_shalrath(); }
--- /dev/null
+// size
+const vector SHAMBLER_MIN = '-32 -32 -24';
+const vector SHAMBLER_MAX = '32 32 64';
+
+// cvars
+float autocvar_g_monster_shambler;
+float autocvar_g_monster_shambler_health;
+float autocvar_g_monster_shambler_damage;
+float autocvar_g_monster_shambler_attack_lightning_damage;
+float autocvar_g_monster_shambler_attack_claw_damage;
+float autocvar_g_monster_shambler_speed_walk;
+float autocvar_g_monster_shambler_speed_run;
+
+// animations
+#define shambler_anim_stand 0
+#define shambler_anim_walk 1
+#define shambler_anim_run 2
+#define shambler_anim_smash 3
+#define shambler_anim_swingr 4
+#define shambler_anim_swingl 5
+#define shambler_anim_magic 6
+#define shambler_anim_pain 7
+#define shambler_anim_death 8
+
+void shambler_think ()
+{
+ self.think = shambler_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_shambler_speed_run, autocvar_g_monster_shambler_speed_walk, 300, shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
+}
+
+void shambler_smash ()
+{
+ float bigdmg = autocvar_g_monster_shambler_damage * self.scale;
+
+ self.think = shambler_think;
+ self.attack_finished_single = time + 0.4;
+ self.nextthink = self.attack_finished_single;
+
+ if (!self.enemy)
+ return;
+
+ if (enemy_range() > 100 * self.scale)
+ return;
+
+ Damage(self.enemy, self, self, bigdmg * monster_skill, DEATH_MONSTER_SHAMBLER_MELEE, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+}
+
+void shambler_delayedsmash ()
+{
+ self.frame = shambler_anim_smash;
+ self.think = shambler_smash;
+ self.nextthink = time + 0.7;
+}
+
+void ShamClaw (float side)
+{
+ float bigdmg = autocvar_g_monster_shambler_attack_claw_damage * self.scale;
+
+ monster_melee(self.enemy, bigdmg * monster_skill, 100, DEATH_MONSTER_SHAMBLER_CLAW);
+}
+
+void() shambler_swing_right;
+void shambler_swing_left ()
+{
+ self.frame = shambler_anim_swingl;
+ ShamClaw(250);
+ self.attack_finished_single = time + 0.8;
+ self.nextthink = self.attack_finished_single;
+ self.think = shambler_think;
+ if(random() < 0.5)
+ self.think = shambler_swing_right;
+}
+
+void shambler_swing_right ()
+{
+ self.frame = shambler_anim_swingr;
+ ShamClaw(-250);
+ self.attack_finished_single = time + 0.8;
+ self.nextthink = self.attack_finished_single;
+ self.think = shambler_think;
+ if(random() < 0.5)
+ self.think = shambler_swing_left;
+}
+
+void sham_melee ()
+{
+ local float chance = random();
+
+ if (chance > 0.6)
+ shambler_delayedsmash();
+ else if (chance > 0.3)
+ shambler_swing_right ();
+ else
+ shambler_swing_left ();
+}
+
+void CastLightning ()
+{
+ self.nextthink = time + 0.4;
+ self.think = shambler_think;
+
+ local vector org = '0 0 0', dir = '0 0 0';
+ vector v = '0 0 0';
+
+ self.effects |= EF_MUZZLEFLASH;
+
+ org = self.origin + '0 0 40' * self.scale;
+
+ dir = self.enemy.origin + '0 0 16' - org;
+ dir = normalize (dir);
+
+ traceline (org, self.origin + dir * 1000, TRUE, self);
+
+ FireRailgunBullet (org, org + dir * 1000, autocvar_g_monster_shambler_attack_lightning_damage * monster_skill, 0, 0, 0, 0, 0, DEATH_MONSTER_SHAMBLER_LIGHTNING);
+
+ // teamcolor / hit beam effect
+ v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+ WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), org, v);
+}
+
+void shambler_magic ()
+{
+ self.frame = shambler_anim_magic;
+ self.attack_finished_single = time + 1.1;
+ self.nextthink = time + 0.6;
+ self.think = CastLightning;
+}
+
+float sham_lightning ()
+{
+ shambler_magic();
+ return TRUE;
+}
+
+void shambler_die ()
+{
+ Monster_CheckDropCvars ("shambler");
+
+ W_ThrowNewWeapon(self, WEP_NEX, 0, self.origin, self.velocity);
+
+ self.think = Monster_Fade;
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.nextthink = time + 2.1;
+ self.frame = shambler_anim_death;
+ self.pain_finished = self.nextthink;
+ self.movetype = MOVETYPE_TOSS;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void shambler_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_shambler_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_shambler";
+ self.attack_melee = sham_melee;
+ self.checkattack = GenericCheckAttack;
+ self.attack_ranged = sham_lightning;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.frame = shambler_anim_stand;
+ self.think = shambler_think;
+ self.sprite_height = 70 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_shambler ()
+{
+ if not(autocvar_g_monster_shambler)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_shambler;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Shambler",
+ "models/monsters/shambler.mdl",
+ SHAMBLER_MIN, SHAMBLER_MAX,
+ FALSE,
+ shambler_die, shambler_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ precache_model ("progs/beam.mdl");
+ precache_model ("models/weapons/g_nex.md3");
+
+ precache_sound ("weapons/lgbeam_fire.wav");
+}
--- /dev/null
+// size
+const vector SOLDIER_MIN = '-16 -16 -30';
+const vector SOLDIER_MAX = '16 16 32';
+
+// cvars
+float autocvar_g_monster_soldier;
+float autocvar_g_monster_soldier_health;
+float autocvar_g_monster_soldier_melee_damage;
+float autocvar_g_monster_soldier_speed_walk;
+float autocvar_g_monster_soldier_speed_run;
+float autocvar_g_monster_soldier_ammo;
+float autocvar_g_monster_soldier_weapon_laser_chance;
+float autocvar_g_monster_soldier_weapon_shotgun_chance;
+float autocvar_g_monster_soldier_weapon_machinegun_chance;
+float autocvar_g_monster_soldier_weapon_rocketlauncher_chance;
+float autocvar_g_monster_soldier_attack_uzi_bullets;
+
+// animations
+#define soldier_anim_stand 0
+#define soldier_anim_death1 1
+#define soldier_anim_death2 2
+#define soldier_anim_reload 3
+#define soldier_anim_pain1 4
+#define soldier_anim_pain2 5
+#define soldier_anim_pain3 6
+#define soldier_anim_run 7
+#define soldier_anim_shoot 8
+#define soldier_anim_prowl 9
+
+void soldier_think ()
+{
+ self.think = soldier_think;
+ self.nextthink = time + 0.1;
+
+ if(self.delay != -1)
+ self.nextthink = self.delay;
+
+ if(time < self.attack_finished_single)
+ monster_move(0, 0, 0, soldier_anim_shoot, soldier_anim_shoot, soldier_anim_shoot);
+ else
+ monster_move(autocvar_g_monster_soldier_speed_run, autocvar_g_monster_soldier_speed_walk, 50, soldier_anim_run, soldier_anim_prowl, soldier_anim_stand);
+}
+
+void soldier_reload ()
+{
+ self.frame = soldier_anim_reload;
+ self.attack_finished_single = time + 2;
+ self.currentammo = autocvar_g_monster_soldier_ammo;
+ sound (self, CH_SHOTS, "weapons/reload.wav", VOL_BASE, ATTN_LARGE);
+}
+
+float SoldierCheckAttack ()
+{
+ local vector spot1 = '0 0 0', spot2 = '0 0 0';
+ local entity targ = self.enemy;
+ local float chance = 0;
+
+ if (self.health <= 0 || targ.health < 1 || targ == world)
+ return FALSE;
+
+ if (vlen(targ.origin - self.origin) > 2000) // long traces are slow
+ return FALSE;
+
+ // see if any entities are in the way of the shot
+ spot1 = self.origin + self.view_ofs;
+ spot2 = targ.origin + targ.view_ofs;
+
+ traceline (spot1, spot2, FALSE, self);
+
+ if (trace_ent != targ)
+ return FALSE; // don't have a clear shot
+
+ if (trace_inwater)
+ if (trace_inopen)
+ return FALSE; // sight line crossed contents
+
+ if(self.monster_delayedattack && self.delay != -1)
+ {
+ if(time < self.delay)
+ return FALSE;
+
+ self.monster_delayedattack();
+ }
+
+ // missile attack
+ if (time < self.attack_finished_single)
+ return FALSE;
+
+ if (enemy_range() >= 2000)
+ return FALSE;
+
+ if (enemy_range() <= 120)
+ chance = 0.9;
+ else if (enemy_range() <= 500)
+ chance = 0.6; // was 0.4
+ else if (enemy_range() <= 1000)
+ chance = 0.3; // was 0.05
+ else
+ chance = 0;
+
+ if (chance > 0)
+ if (chance > random())
+ return FALSE;
+
+ if(self.currentammo <= 0 && enemy_range() <= 120)
+ {
+ self.attack_melee();
+ return TRUE;
+ }
+
+ if(self.currentammo <= 0)
+ {
+ soldier_reload();
+ return FALSE;
+ }
+
+ if (self.attack_ranged())
+ return TRUE;
+
+ return FALSE;
+}
+
+void soldier_laser ()
+{
+ self.frame = soldier_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+ W_Laser_Attack(0);
+}
+
+float soldier_missile_laser ()
+{
+ // FIXME: check if it would hit
+ soldier_laser();
+ return TRUE;
+}
+
+.float grunt_cycles;
+void soldier_uzi_fire ()
+{
+ self.currentammo -= 1;
+ if(self.currentammo <= 0)
+ return;
+
+ self.grunt_cycles += 1;
+
+ if(self.grunt_cycles > autocvar_g_monster_soldier_attack_uzi_bullets)
+ {
+ self.monster_delayedattack = func_null;
+ self.delay = -1;
+ return;
+ }
+ W_UZI_Attack(DEATH_MONSTER_SOLDIER_NAIL);
+ self.delay = time + 0.1;
+ self.monster_delayedattack = soldier_uzi_fire;
+}
+
+void soldier_uzi ()
+{
+ if(self.currentammo <= 0)
+ return;
+
+ self.frame = soldier_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+ self.delay = time + 0.1;
+ self.monster_delayedattack = soldier_uzi_fire;
+}
+
+float soldier_missile_uzi ()
+{
+ self.grunt_cycles = 0;
+ // FIXME: check if it would hit
+ soldier_uzi();
+ return TRUE;
+}
+
+void soldier_shotgun ()
+{
+ self.currentammo -= 1;
+ if(self.currentammo <= 0)
+ return;
+
+ self.frame = soldier_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+ W_Shotgun_Attack();
+}
+
+float soldier_missile_shotgun ()
+{
+ // FIXME: check if it would hit
+ self.grunt_cycles = 0;
+ soldier_shotgun();
+ return TRUE;
+}
+
+void soldier_rl ()
+{
+ self.currentammo -= 1;
+ if(self.currentammo <= 0)
+ return;
+
+ self.frame = soldier_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+ W_Rocket_Attack();
+}
+
+float soldier_missile_rl ()
+{
+ // FIXME: check if it would hit
+ soldier_rl();
+ return TRUE;
+}
+
+void soldier_bash ()
+{
+ self.frame = soldier_anim_shoot;
+ self.attack_finished_single = time + 0.8;
+ monster_melee(self.enemy, autocvar_g_monster_soldier_melee_damage, 70, DEATH_MONSTER_SOLDIER_NAIL);
+}
+
+void soldier_die()
+{
+ Monster_CheckDropCvars ("soldier");
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.think = Monster_Fade;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+
+ if (self.attack_ranged == soldier_missile_uzi)
+ W_ThrowNewWeapon(self, WEP_UZI, 0, self.origin, self.velocity);
+ else if (self.attack_ranged == soldier_missile_shotgun)
+ W_ThrowNewWeapon(self, WEP_SHOTGUN, 0, self.origin, self.velocity);
+ else if (self.attack_ranged == soldier_missile_rl)
+ W_ThrowNewWeapon(self, WEP_ROCKET_LAUNCHER, 0, self.origin, self.velocity);
+ else
+ W_ThrowNewWeapon(self, WEP_LASER, 0, self.origin, self.velocity);
+
+ if (random() < 0.5)
+ self.frame = soldier_anim_death1;
+ else
+ self.frame = soldier_anim_death2;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void soldier_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_soldier_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_soldier";
+ self.checkattack = SoldierCheckAttack;
+ self.attack_melee = soldier_bash;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = soldier_think;
+ self.sprite_height = 30 * self.scale;
+ self.items = (IT_SHELLS | IT_ROCKETS | IT_NAILS);
+
+ RandomSelection_Init();
+ RandomSelection_Add(world, WEP_LASER, string_null, autocvar_g_monster_soldier_weapon_laser_chance, 1);
+ RandomSelection_Add(world, WEP_SHOTGUN, string_null, autocvar_g_monster_soldier_weapon_shotgun_chance, 1);
+ RandomSelection_Add(world, WEP_UZI, string_null, autocvar_g_monster_soldier_weapon_machinegun_chance, 1);
+ RandomSelection_Add(world, WEP_ROCKET_LAUNCHER, string_null, autocvar_g_monster_soldier_weapon_rocketlauncher_chance, 1);
+
+ if (RandomSelection_chosen_float == WEP_ROCKET_LAUNCHER)
+ {
+ self.weapon = WEP_ROCKET_LAUNCHER;
+ self.currentammo = self.ammo_rockets;
+ self.armorvalue = 10;
+ self.attack_ranged = soldier_missile_rl;
+ }
+ else if (RandomSelection_chosen_float == WEP_UZI)
+ {
+ self.weapon = WEP_UZI;
+ self.currentammo = self.ammo_nails;
+ self.armorvalue = 100;
+ self.attack_ranged = soldier_missile_uzi;
+ }
+ else if (RandomSelection_chosen_float == WEP_SHOTGUN)
+ {
+ self.weapon = WEP_SHOTGUN;
+ self.currentammo = self.ammo_shells;
+ self.armorvalue = 25;
+ self.attack_ranged = soldier_missile_shotgun;
+ }
+ else
+ {
+ self.weapon = WEP_LASER;
+ self.armorvalue = 60;
+ self.currentammo = self.ammo_none;
+ self.attack_ranged = soldier_missile_laser;
+ }
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_soldier ()
+{
+ if not(autocvar_g_monster_soldier)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_soldier;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Grunt",
+ "models/monsters/soldier.mdl",
+ SOLDIER_MIN, SOLDIER_MAX,
+ FALSE,
+ soldier_die, soldier_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ precache_sound ("weapons/shotgun_fire.wav");
+ precache_sound ("weapons/uzi_fire.wav");
+ precache_sound ("weapons/laser_fire.wav");
+ precache_sound ("weapons/reload.wav");
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_army () { spawnfunc_monster_soldier(); }
--- /dev/null
+// size
+const vector SPAWNER_MIN = '-35 -35 -10';
+const vector SPAWNER_MAX = '35 35 70';
+
+// cvars
+float autocvar_g_monster_spawner;
+float autocvar_g_monster_spawner_health;
+float autocvar_g_monster_spawner_maxmobs;
+string autocvar_g_monster_spawner_forcespawn;
+
+void() spawner_think;
+
+void spawnmonsters ()
+{
+ if(self.spawner_monstercount >= autocvar_g_monster_spawner_maxmobs || self.frozen || self.freezetag_frozen)
+ return;
+
+ vector p1, p2, p3, p4, chosenposi;
+ float r = random();
+ string type = "";
+ entity e;
+
+ self.spawner_monstercount += 1;
+
+ if(self.spawnmob != "")
+ type = self.spawnmob;
+
+ if(autocvar_g_monster_spawner_forcespawn != "")
+ type = autocvar_g_monster_spawner_forcespawn;
+
+ if(type == "" || type == "spawner") // spawner spawning spawners?!
+ type = "knight";
+
+ p1 = self.origin - '0 70 -50' * self.scale;
+ p2 = self.origin + '0 70 50' * self.scale;
+ p3 = self.origin - '70 0 -50' * self.scale;
+ p4 = self.origin + '70 0 -50' * self.scale;
+
+ if (r < 0.20)
+ chosenposi = p1;
+ else if (r < 0.50)
+ chosenposi = p2;
+ else if (r < 80)
+ chosenposi = p3;
+ else
+ chosenposi = p4;
+
+ e = spawnmonster(type, self, self, chosenposi, FALSE, MONSTER_MOVE_WANDER);
+
+ if(teamplay && autocvar_g_monsters_teams)
+ e.team = self.team;
+
+ if(self.spawnflags & MONSTERFLAG_GIANT)
+ e.spawnflags = MONSTERFLAG_GIANT;
+
+ if(self.flags & MONSTERFLAG_MINIBOSS)
+ e.spawnflags = MONSTERFLAG_MINIBOSS;
+}
+
+void spawner_die ()
+{
+ setmodel(self, "");
+ pointparticles(particleeffectnum(((self.scale > 3) ? "explosion_big" : "explosion_medium")), self.origin, '0 0 0', 1);
+ sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.think = Monster_Fade;
+ self.nextthink = time + 1;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void spawner_think()
+{
+ float finished = FALSE;
+ self.think = spawner_think;
+
+ if(self.spawner_monstercount >= autocvar_g_monster_spawner_maxmobs)
+ {
+ self.nextthink = time + 5;
+ }
+
+ if (self.spawner_monstercount <= autocvar_g_monster_spawner_maxmobs)
+ {
+ spawnmonsters();
+ finished = TRUE;
+ }
+
+ self.nextthink = time + 1;
+
+ if(self.spawner_monstercount <= autocvar_g_monster_spawner_maxmobs || !finished)
+ self.nextthink = time + 0.1;
+}
+
+void spawner_spawn()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_spawner_health * self.scale;
+
+ self.classname = "monster_spawner";
+ self.nextthink = time + 0.2;
+ self.velocity = '0 0 0';
+ self.think = spawner_think;
+ self.touch = func_null;
+ self.sprite_height = 80 * self.scale;
+
+ self.spawner_monstercount = 0;
+
+ droptofloor();
+ self.movetype = MOVETYPE_NONE;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+/*QUAKED monster_spawner (1 0 0) (-18 -18 -25) (18 18 47)
+---------NOTES----------
+Spawns monsters when a player is nearby
+-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
+modeldisabled="models/containers/crate01.md3"
+*/
+void spawnfunc_monster_spawner()
+{
+ if not(autocvar_g_monster_spawner)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_spawner;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 0.8;
+
+ if not (monster_initialize(
+ "Monster spawner",
+ "models/containers/crate01.md3",
+ SPAWNER_MIN, SPAWNER_MAX,
+ FALSE,
+ spawner_die, spawner_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ precache_sound("weapons/rocket_impact.wav");
+}
--- /dev/null
+// cvars
+float autocvar_g_monster_spider;
+float autocvar_g_monster_spider_stopspeed;
+float autocvar_g_monster_spider_attack_leap_delay;
+float autocvar_g_monster_spider_attack_stand_damage;
+float autocvar_g_monster_spider_attack_stand_delay;
+float autocvar_g_monster_spider_health;
+float autocvar_g_monster_spider_speed_walk;
+float autocvar_g_monster_spider_speed_run;
+float autocvar_g_monster_spider_attack_type;
+
+// spider animations
+#define spider_anim_idle 0
+#define spider_anim_walk 1
+#define spider_anim_attack 2
+#define spider_anim_attack2 3
+
+const vector SPIDER_MIN = '-18 -18 -25';
+const vector SPIDER_MAX = '18 18 30';
+
+.float spider_type; // used to switch between fire & ice attacks
+const float SPIDER_TYPE_ICE = 0;
+const float SPIDER_TYPE_FIRE = 1;
+
+void spider_spawn();
+void spawnfunc_monster_spider();
+void spider_think();
+
+void spider_die ()
+{
+ Monster_CheckDropCvars ("spider");
+
+ self.angles += '180 0 0';
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.think = Monster_Fade;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+ self.frame = spider_anim_attack;
+
+ monster_hook_death(); // for post-death mods
+}
+
+/**
+ * Performe a standing attack on self.enemy.
+ */
+void spider_attack_standing() {
+ float dot = 0, bigdmg = autocvar_g_monster_spider_attack_stand_damage * self.scale;
+
+ self.velocity_x = 0;
+ self.velocity_y = 0;
+
+ if(self.monster_owner == self.enemy)
+ {
+ self.enemy = world;
+ return;
+ }
+
+ makevectors (self.angles);
+ dot = normalize (self.enemy.origin - self.origin) * v_forward;
+ if(dot > 0.3)
+ {
+ Damage(self.enemy, self, self, bigdmg * monster_skill, DEATH_MONSTER_MELEE, self.origin, '0 0 0');
+ }
+
+ if (!monster_isvalidtarget(self.enemy, self, FALSE))
+ self.enemy = world;
+
+ if(random() < 0.50)
+ self.frame = spider_anim_attack;
+ else
+ self.frame = spider_anim_attack2;
+
+ self.nextthink = time + autocvar_g_monster_spider_attack_stand_delay;
+}
+
+void spider_web_explode ()
+{
+ RadiusDamage (self, self.realowner, 0, 0, 1, world, 0, self.projectiledeathtype, other);
+ remove (self);
+}
+
+void spider_web_touch ()
+{
+ PROJECTILE_TOUCH;
+ if (other.takedamage == DAMAGE_AIM)
+ Freeze(other, 0.3);
+
+ spider_web_explode();
+}
+
+void spider_shootweb()
+{
+ // clone of the electro secondary attack, with less bouncing
+ entity proj = world;
+
+ makevectors(self.angles);
+
+ W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, 0);
+
+ w_shotdir = v_forward; // no TrueAim for grenades please
+
+ pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ proj = spawn ();
+ proj.classname = "plasma";
+ proj.owner = proj.realowner = self;
+ proj.use = spider_web_touch;
+ proj.think = adaptor_think2use_hittype_splash;
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = 0;
+ proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime;
+ PROJECTILE_MAKETRIGGER(proj);
+ proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY;
+ setorigin(proj, w_shotorg);
+
+ //proj.glow_size = 50;
+ //proj.glow_color = 45;
+ proj.movetype = MOVETYPE_BOUNCE;
+ W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary);
+ proj.touch = spider_web_touch;
+ setsize(proj, '0 0 -4', '0 0 -4');
+ proj.takedamage = DAMAGE_YES;
+ proj.damageforcescale = 0;
+ proj.health = 500;
+ proj.event_damage = W_Plasma_Damage;
+ proj.flags = FL_PROJECTILE;
+ proj.damagedbycontents = TRUE;
+
+ proj.bouncefactor = 0.3;
+ proj.bouncestop = 0.05;
+ proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+ CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void spider_attack_leap()
+{
+ vector angles_face = vectoangles(self.enemy.origin - self.origin);
+
+ // face the enemy
+ self.frame = spider_anim_attack2;
+ self.angles_y = angles_face_y ;
+ self.nextthink = time + autocvar_g_monster_spider_attack_leap_delay;
+
+ makevectors(self.angles);
+
+ switch(self.spider_type)
+ {
+ default:
+ case SPIDER_TYPE_ICE:
+ spider_shootweb(); break; // must... remember... breaks!
+ case SPIDER_TYPE_FIRE:
+ W_Fireball_Attack2(); break;
+ }
+}
+
+float spider_attack_ranged()
+{
+ if(self.enemy.frozen || self.enemy.freezetag_frozen)
+ return FALSE;
+
+ spider_attack_leap();
+ return TRUE;
+}
+
+void spider_think()
+{
+ self.think = spider_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_spider_speed_run, autocvar_g_monster_spider_speed_walk, autocvar_g_monster_spider_stopspeed, spider_anim_walk, spider_anim_walk, spider_anim_idle);
+}
+
+/**
+ * Spawn the spider.
+ */
+void spider_spawn()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_spider_health * self.scale;
+
+ self.classname = "monster_spider";
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.pain_finished = self.nextthink;
+ self.frame = spider_anim_idle;
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = spider_attack_standing;
+ self.attack_ranged = spider_attack_ranged;
+ self.think = spider_think;
+ self.sprite_height = 40 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+/*QUAKED monster_spider (1 0 0) (-18 -18 -25) (18 18 47)
+Spider, 60 health points.
+-------- KEYS --------
+-------- SPAWNFLAGS --------
+MONSTERFLAG_APPEAR: monster will spawn when triggered.
+---------NOTES----------
+-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
+modeldisabled="models/monsters/spider.dpm"
+*/
+void spawnfunc_monster_spider()
+{
+ if not(autocvar_g_monster_spider)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_spider;
+ self.classname = "monster_spider";
+ if(!self.spider_type)
+ self.spider_type = autocvar_g_monster_spider_attack_type;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ if not (monster_initialize(
+ "Spider",
+ "models/monsters/spider.dpm",
+ SPIDER_MIN, SPIDER_MAX,
+ FALSE,
+ spider_die, spider_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
--- /dev/null
+// size
+const vector TARBABY_MIN = '-16 -16 -24';
+const vector TARBABY_MAX = '16 16 16';
+
+// cvars
+float autocvar_g_monster_tarbaby;
+float autocvar_g_monster_tarbaby_health;
+float autocvar_g_monster_tarbaby_speed_walk;
+float autocvar_g_monster_tarbaby_speed_run;
+
+// animations
+#define tarbaby_anim_walk 0
+#define tarbaby_anim_run 1
+#define tarbaby_anim_jump 2
+#define tarbaby_anim_fly 3
+#define tarbaby_anim_explode 4
+
+void tarbaby_think ()
+{
+ self.think = tarbaby_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_tarbaby_speed_run, autocvar_g_monster_tarbaby_speed_walk, 20, tarbaby_anim_run, tarbaby_anim_walk, tarbaby_anim_walk);
+}
+
+void Tar_JumpTouch ()
+{
+ // dunno why this would be called when dead, but to be safe
+ if (self.health <= 0)
+ return;
+
+ if (other.takedamage)
+ if (vlen(self.velocity) > 200)
+ {
+ // make the monster die
+ self.event_damage(self, self, self.health + self.max_health, DEATH_TOUCHEXPLODE, self.origin, '0 0 0');
+
+ return;
+ }
+
+ if (trace_dphitcontents)
+ {
+ if not(self.flags & FL_ONGROUND)
+ {
+ self.touch = MonsterTouch;
+ self.flags |= FL_ONGROUND;
+ self.movetype = MOVETYPE_WALK;
+ }
+ }
+}
+
+void tarbaby_jump ()
+{
+ if not(self.flags & FL_ONGROUND)
+ return;
+ self.frame = tarbaby_anim_jump;
+ // dunno why this would be called when dead, but to be safe
+ if (self.health <= 0)
+ return;
+ self.movetype = MOVETYPE_BOUNCE;
+ self.touch = Tar_JumpTouch;
+ makevectors (self.angles);
+ self.origin_z += 1;
+ self.velocity = v_forward * 600 + '0 0 200';
+ self.velocity_z += random()*150;
+ if (self.flags & FL_ONGROUND)
+ self.flags -= FL_ONGROUND;
+
+ self.attack_finished_single = time + 0.5;
+}
+
+float tbaby_jump ()
+{
+ tarbaby_jump();
+ return TRUE;
+}
+
+void tarbaby_blowup ()
+{
+ float bigboom = 250 * (self.scale * 0.7);
+ RadiusDamage(self, self, 250 * monster_skill, 15, bigboom * (monster_skill * 0.7), world, 250, DEATH_MONSTER_TARBABY_BLOWUP, world);
+ pointparticles(particleeffectnum(((self.scale > 3) ? "explosion_big" : "explosion_medium")), self.origin, '0 0 0', 1);
+ sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+
+ Monster_CheckDropCvars ("tarbaby"); // drop items after exploding to prevent player picking up item before dying
+
+ setmodel(self, "");
+}
+
+void tarbaby_explode()
+{
+ tarbaby_blowup();
+
+ monster_hook_death(); // calling this next frame should be ok...
+}
+
+void tarbaby_die ()
+{
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.movetype = MOVETYPE_NONE;
+ self.enemy = world;
+ self.think = tarbaby_explode;
+ self.nextthink = time + 0.1;
+}
+
+void tarbaby_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_tarbaby_health * self.scale;
+
+ self.damageforcescale = 0.003;
+ self.classname = "monster_tarbaby";
+ self.checkattack = GenericCheckAttack;
+ self.attack_ranged = tbaby_jump;
+ self.attack_melee = tarbaby_jump;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.think = tarbaby_think;
+ self.sprite_height = 20 * self.scale;
+ self.frame = tarbaby_anim_walk;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_tarbaby ()
+{
+ if not(autocvar_g_monster_tarbaby)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_tarbaby;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Spawn",
+ "models/monsters/tarbaby.mdl",
+ TARBABY_MIN, TARBABY_MAX,
+ FALSE,
+ tarbaby_die, tarbaby_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ precache_sound ("weapons/rocket_impact.wav");
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_spawn () { spawnfunc_monster_tarbaby(); }
--- /dev/null
+// size
+const vector WIZARD_MIN = '-16 -16 -24';
+const vector WIZARD_MAX = '16 16 24';
+
+// cvars
+float autocvar_g_monster_wizard;
+float autocvar_g_monster_wizard_health;
+float autocvar_g_monster_wizard_speed_walk;
+float autocvar_g_monster_wizard_speed_run;
+float autocvar_g_monster_wizard_spike_damage;
+float autocvar_g_monster_wizard_spike_edgedamage;
+float autocvar_g_monster_wizard_spike_radius;
+float autocvar_g_monster_wizard_spike_speed;
+
+// animations
+#define wizard_anim_hover 0
+#define wizard_anim_fly 1
+#define wizard_anim_magic 2
+#define wizard_anim_pain 3
+#define wizard_anim_death 4
+
+void Wiz_FastExplode()
+{
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+ RadiusDamage (self, self.realowner, autocvar_g_monster_wizard_spike_damage, autocvar_g_monster_wizard_spike_edgedamage, autocvar_g_monster_wizard_spike_radius, world, 0, self.projectiledeathtype, other);
+
+ remove (self);
+}
+
+void Wiz_FastTouch ()
+{
+ PROJECTILE_TOUCH;
+
+ if(other == self.owner)
+ return;
+
+ if(teamplay)
+ if(other.team == self.owner.team)
+ return;
+
+ pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
+
+ Wiz_FastExplode();
+}
+
+void Wiz_StartFast ()
+{
+ local entity missile;
+ local vector dir = '0 0 0';
+ local float dist = 0, flytime = 0;
+
+ dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+ dist = vlen (self.enemy.origin - self.origin);
+ flytime = dist * 0.002;
+ if (flytime < 0.1)
+ flytime = 0.1;
+
+ self.v_angle = self.angles;
+ makevectors (self.angles);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ setsize (missile, '0 0 0', '0 0 0');
+ setorigin (missile, self.origin + v_forward * 14 + '0 0 30' + v_right * 14);
+ missile.enemy = self.enemy;
+ missile.nextthink = time + 3;
+ missile.think = Wiz_FastExplode;
+ missile.velocity = dir * autocvar_g_monster_wizard_spike_speed;
+ missile.avelocity = '300 300 300';
+ missile.solid = SOLID_BBOX;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.touch = Wiz_FastTouch;
+ CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ setsize (missile, '0 0 0', '0 0 0');
+ setorigin (missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
+ missile.enemy = self.enemy;
+ missile.nextthink = time + 3;
+ missile.touch = Wiz_FastTouch;
+ missile.solid = SOLID_BBOX;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.think = Wiz_FastExplode;
+ missile.velocity = dir * autocvar_g_monster_wizard_spike_speed;
+ missile.avelocity = '300 300 300';
+ CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void wizard_think ()
+{
+ self.think = wizard_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_wizard_speed_run, autocvar_g_monster_wizard_speed_walk, 300, wizard_anim_fly, wizard_anim_hover, wizard_anim_hover);
+}
+
+void wizard_fastattack ()
+{
+ Wiz_StartFast();
+}
+
+void wizard_die ()
+{
+ Monster_CheckDropCvars ("wizard");
+
+ self.think = Monster_Fade;
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.flags = FL_ONGROUND;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+ self.velocity_x = -200 + 400*random();
+ self.velocity_y = -200 + 400*random();
+ self.velocity_z = 100 + 100*random();
+ self.frame = wizard_anim_death;
+
+ monster_hook_death(); // for post-death mods
+}
+
+float Wiz_Missile ()
+{
+ wizard_fastattack();
+ return TRUE;
+}
+
+void wizard_spawn ()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_wizard_health * self.scale;
+
+ self.classname = "monster_wizard";
+ self.checkattack = GenericCheckAttack;
+ self.attack_ranged = Wiz_Missile;
+ self.nextthink = time + random() * 0.5 + 0.1;
+ self.movetype = MOVETYPE_FLY; // TODO: make it fly up/down
+ self.flags |= FL_FLY;
+ self.think = wizard_think;
+ self.sprite_height = 30 * self.scale;
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_wizard ()
+{
+ if not(autocvar_g_monster_wizard)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_wizard;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ self.scale = 1.3;
+
+ if not (monster_initialize(
+ "Scrag",
+ "models/monsters/wizard.mdl",
+ WIZARD_MIN, WIZARD_MAX,
+ TRUE,
+ wizard_die, wizard_spawn))
+ {
+ remove(self);
+ return;
+ }
+
+ precache_model ("models/spike.mdl");
+ precache_sound ("weapons/spike.wav");
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_scrag () { spawnfunc_monster_wizard(); }
--- /dev/null
+/**
+ * Special purpose fields:
+ * .delay - time at which to check if zombie's enemy is still in range
+ * .enemy - enemy of this zombie
+ */
+
+// cvars
+float autocvar_g_monster_zombie;
+float autocvar_g_monster_zombie_stopspeed;
+float autocvar_g_monster_zombie_attack_leap_damage;
+float autocvar_g_monster_zombie_attack_leap_delay;
+float autocvar_g_monster_zombie_attack_leap_force;
+float autocvar_g_monster_zombie_attack_leap_speed;
+float autocvar_g_monster_zombie_attack_stand_damage;
+float autocvar_g_monster_zombie_attack_stand_delay;
+float autocvar_g_monster_zombie_health;
+float autocvar_g_monster_zombie_speed_walk;
+float autocvar_g_monster_zombie_speed_run;
+
+// zombie animations
+#define zombie_anim_attackleap 0
+#define zombie_anim_attackrun1 1
+#define zombie_anim_attackrun2 2
+#define zombie_anim_attackrun3 3
+#define zombie_anim_attackstanding1 4
+#define zombie_anim_attackstanding2 5
+#define zombie_anim_attackstanding3 6
+#define zombie_anim_blockend 7
+#define zombie_anim_blockstart 8
+#define zombie_anim_deathback1 9
+#define zombie_anim_deathback2 10
+#define zombie_anim_deathback3 11
+#define zombie_anim_deathfront1 12
+#define zombie_anim_deathfront2 13
+#define zombie_anim_deathfront3 14
+#define zombie_anim_deathleft1 15
+#define zombie_anim_deathleft2 16
+#define zombie_anim_deathright1 17
+#define zombie_anim_deathright2 18
+#define zombie_anim_idle 19
+#define zombie_anim_painback1 20
+#define zombie_anim_painback2 21
+#define zombie_anim_painfront1 22
+#define zombie_anim_painfront2 23
+#define zombie_anim_runbackwards 24
+#define zombie_anim_runbackwardsleft 25
+#define zombie_anim_runbackwardsright 26
+#define zombie_anim_runforward 27
+#define zombie_anim_runforwardleft 28
+#define zombie_anim_runforwardright 29
+#define zombie_anim_spawn 30
+
+const vector ZOMBIE_MIN = '-18 -18 -25';
+const vector ZOMBIE_MAX = '18 18 47';
+
+void zombie_spawn();
+void spawnfunc_monster_zombie();
+void zombie_think();
+
+void zombie_die ()
+{
+ Monster_CheckDropCvars ("zombie");
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.think = Monster_Fade;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+
+ if (random() > 0.5)
+ self.frame = zombie_anim_deathback1;
+ else
+ self.frame = zombie_anim_deathfront1;
+
+ monster_hook_death(); // for post-death mods
+}
+
+void zombie_attack_standing()
+{
+ float rand = random(), dot = 0, bigdmg = 0;
+
+ self.velocity_x = 0;
+ self.velocity_y = 0;
+
+ if(self.monster_owner == self.enemy)
+ {
+ self.enemy = world;
+ return;
+ }
+
+ bigdmg = autocvar_g_monster_zombie_attack_stand_damage * self.scale;
+
+ //print("zombie attacks!\n");
+ makevectors (self.angles);
+ dot = normalize (self.enemy.origin - self.origin) * v_forward;
+ if(dot > 0.3)
+ {
+ Damage(self.enemy, self, self, bigdmg * monster_skill, DEATH_MONSTER_MELEE, self.origin, '0 0 0');
+ }
+
+ if (!monster_isvalidtarget(self.enemy, self, FALSE))
+ self.enemy = world;
+
+ if (rand < 0.33)
+ self.frame = zombie_anim_attackstanding1;
+ else if (rand < 0.66)
+ self.frame = zombie_anim_attackstanding2;
+ else
+ self.frame = zombie_anim_attackstanding3;
+
+ self.nextthink = time + autocvar_g_monster_zombie_attack_stand_delay;
+ self.attack_finished_single = self.nextthink;
+}
+
+void zombie_attack_leap_touch()
+{
+ vector angles_face;
+ float bigdmg = autocvar_g_monster_zombie_attack_leap_damage * self.scale;
+
+ if (other.deadflag != DEAD_NO)
+ return;
+
+ if (self.monster_owner == other)
+ return;
+
+ if (other.takedamage == DAMAGE_NO)
+ return;
+
+ //void Damage (entity targ, entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+ traceline(self.origin, other.origin, FALSE, self);
+
+ angles_face = vectoangles(self.moveto - self.origin);
+ angles_face = normalize(angles_face) * autocvar_g_monster_zombie_attack_leap_force;
+ Damage(other, self, self, bigdmg * monster_skill, DEATH_MONSTER_MELEE, trace_endpos, angles_face);
+
+ self.touch = MonsterTouch;
+}
+
+float zombie_attack_ranged()
+{
+ makevectors(self.angles);
+ if(monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * autocvar_g_monster_zombie_attack_leap_speed + '0 0 200', autocvar_g_monster_zombie_attack_leap_delay))
+ return TRUE;
+
+ return FALSE;
+}
+
+void zombie_think()
+{
+ self.think = zombie_think;
+ self.nextthink = time + 0.1;
+
+ monster_move(autocvar_g_monster_zombie_speed_run, autocvar_g_monster_zombie_speed_walk, autocvar_g_monster_zombie_stopspeed, zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
+}
+
+void zombie_spawn()
+{
+ if not(self.health)
+ self.health = autocvar_g_monster_zombie_health * self.scale;
+
+ self.classname = "monster_zombie";
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+ self.frame = zombie_anim_spawn;
+ self.think = zombie_think;
+ self.sprite_height = 50 * self.scale;
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = zombie_attack_standing;
+ self.attack_ranged = zombie_attack_ranged;
+ self.skin = rint(random() * 4);
+
+ monster_hook_spawn(); // for post-spawn mods
+}
+
+/*QUAKED monster_zombie (1 0 0) (-18 -18 -25) (18 18 47)
+Zombie, 60 health points.
+-------- KEYS --------
+-------- SPAWNFLAGS --------
+MONSTERFLAG_APPEAR: monster will spawn when triggered.
+---------NOTES----------
+Original Quake 1 zombie entity used a smaller box ('-16 -16 -24', '16 16 32').
+-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
+modeldisabled="models/monsters/zombie.dpm"
+*/
+void spawnfunc_monster_zombie()
+{
+ if not(autocvar_g_monster_zombie)
+ {
+ remove(self);
+ return;
+ }
+
+ self.monster_spawnfunc = spawnfunc_monster_zombie;
+
+ if(self.spawnflags & MONSTERFLAG_APPEAR)
+ {
+ self.think = func_null;
+ self.nextthink = -1;
+ self.use = Monster_Appear;
+ return;
+ }
+
+ if not (monster_initialize(
+ "Zombie",
+ "models/monsters/zombie.dpm",
+ ZOMBIE_MIN, ZOMBIE_MAX,
+ FALSE,
+ zombie_die, zombie_spawn))
+ {
+ remove(self);
+ return;
+ }
+}
--- /dev/null
+// Lib
+#include "lib/defs.qh"
+#include "lib/monsters.qc"
+
+// Monsters
+#include "lib/spawn.qc"
+#include "monster/ogre.qc"
+#include "monster/demon.qc"
+#include "monster/shambler.qc"
+#include "monster/knight.qc"
+#include "monster/soldier.qc"
+#include "monster/wizard.qc"
+#include "monster/dog.qc"
+#include "monster/tarbaby.qc"
+#include "monster/hknight.qc"
+#include "monster/fish.qc"
+#include "monster/shalrath.qc"
+#include "monster/enforcer.qc"
+#include "monster/zombie.qc"
+#include "monster/spider.qc"
+#include "monster/spawner.qc"
+string monsterlist () { return "ogre demon shambler knight soldier scrag dog spawn hellknight fish vore enforcer zombie spawner spider"; }
\ No newline at end of file
self.velocity = normalize(self.velocity) * (mspeed - 50);//* max_velocity;
}
+void movelib_move_simple_gravity(vector newdir,float velo,float blendrate)
+{
+ float z_speed = self.velocity_z;
+ self.movelib_lastupdate = time;
+ self.velocity = self.velocity * (1 - blendrate) + (newdir * blendrate) * velo;
+ self.velocity_z = z_speed;
+}
+
+void movelib_jump_simple(float power){
+ self.velocity_z=power;
+ self.movelib_lastupdate = time;
+}
+
/*
.float mass;
.float side_friction;
// return error to request removal
// INPUT: self - turret
+MUTATOR_HOOKABLE(TurretValidateTarget);
+ // return target score
+ // INPUT:
+ entity turret_target;
+ entity turret;
+
MUTATOR_HOOKABLE(OnEntityPreSpawn);
// return error to prevent entity spawn, or modify the entity
// INPUT:
entity self;
entity other;
+
+MUTATOR_HOOKABLE(MonsterSpawn);
+ // called when a monster spawns
+
+MUTATOR_HOOKABLE(MonsterDies);
+ // called when a monster dies
+ // INPUT:
+ entity frag_attacker;
+
+MUTATOR_HOOKABLE(MonsterDropItem);
+ // called when a monster is dropping loot
+ // INPUT, OUTPUT:
+ string monster_dropitem;
+ string monster_dropsize;
+
+MUTATOR_HOOKABLE(MonsterMove);
+ // called when a monster moves
+ // INPUT:
+ float monster_speed_run;
+ float monster_speed_walk;
+ entity monster_target;
+
+MUTATOR_HOOKABLE(MonsterFindTarget);
+ // called when a monster looks for another target
+
+MUTATOR_HOOKABLE(MonsterCheckBossFlag);
+ // called to change a random monster to a miniboss
MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor);
// called when a player gets damaged to e.g. remove stuff he was carrying.
// INPUT
entity self; // the player who pressed impulse 33
+MUTATOR_HOOKABLE(VehicleSpawn);
+ // called when a vehicle spawns
+
MUTATOR_HOOKABLE(VehicleEnter);
// called when a player enters a vehicle
// allows mutators to set special settings in this event
--- /dev/null
+// Tower Defense
+// Gamemode by Mario
+
+void spawnfunc_td_controller()
+{
+ if not(g_td)
+ {
+ remove(self);
+ return;
+ }
+ if(autocvar_g_td_force_settings)
+ {
+ self.dontend = FALSE;
+ self.maxwaves = 0;
+ self.monstercount = 0;
+ self.startwave = 0;
+ self.maxturrets = 0;
+ }
+
+ self.netname = "Tower Defense controller entity";
+ self.classname = "td_controller";
+
+ gensurvived = FALSE;
+ td_dont_end = ((self.dontend) ? self.dontend : autocvar_g_td_generator_dontend);
+ max_waves = ((self.maxwaves) ? self.maxwaves : autocvar_g_td_max_waves);
+ totalmonsters = ((self.monstercount) ? self.monstercount : autocvar_g_td_monster_count);
+ wave_count = ((self.startwave) ? self.startwave : autocvar_g_td_start_wave);
+ max_turrets = ((self.maxturrets) ? self.maxturrets : autocvar_g_td_turret_max);
+ build_time = ((self.buildtime) ? self.buildtime : autocvar_g_td_buildphase_time);
+
+ wave_end(TRUE);
+}
+
+void td_generator_die()
+{
+ entity tail;
+
+ print((td_gencount > 1) ? "A generator was destroyed!\n" : "The generator was destroyed.\n");
+
+ if(autocvar_sv_eventlog)
+ GameLogEcho(":gendestroyed");
+
+ gendestroyed = TRUE;
+
+ FOR_EACH_PLAYER(tail)
+ {
+ Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, ((td_gencount > 1) ? "A generator was destroyed!" : "The generator was destroyed."), 0, 0);
+ }
+
+ setmodel(self, "models/onslaught/generator_dead.md3");
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ td_gencount -= 1;
+
+ pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+
+ WaypointSprite_Kill(self.sprite);
+}
+
+void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(attacker.classname == STR_PLAYER || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
+ return;
+
+ entity tail;
+
+ FOR_EACH_PLAYER(tail)
+ {
+ Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "The generator is under attack!", 0, 0);
+ }
+
+ self.health -= damage;
+
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ if(self.health <= 0)
+ td_generator_die();
+}
+
+void spawnfunc_td_generator()
+{
+ if not(g_td)
+ {
+ remove(self);
+ return;
+ }
+
+ gendestroyed = FALSE;
+
+ if not(self.health)
+ self.health = autocvar_g_td_generator_health;
+
+ // precache generator model
+ precache_model("models/onslaught/generator.md3");
+ precache_model("models/onslaught/generator_dead.md3");
+
+ self.model = "models/onslaught/generator.md3";
+ setmodel(self, self.model);
+ self.classname = "td_generator";
+ self.solid = SOLID_BBOX;
+ self.takedamage = DAMAGE_AIM;
+ self.event_damage = td_generator_damage;
+ self.enemy = world;
+ self.nextthink = -1;
+ self.think = func_null;
+ self.max_health = self.health;
+ self.movetype = MOVETYPE_NONE;
+ self.monster_attack = TRUE;
+ td_gencount += 1;
+ self.netname = "Generator";
+
+ setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+
+ droptofloor();
+
+ WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 60', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0');
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+entity PickGenerator()
+{
+ entity generator, head;
+ if(td_gencount == 1)
+ generator = find(world, classname, "td_generator");
+ else
+ {
+ RandomSelection_Init();
+ for(head = world;(head = find(head, classname, "td_generator")); )
+ {
+ RandomSelection_Add(head, 0, string_null, 1, 1);
+ }
+ generator = RandomSelection_chosen_ent;
+ }
+ return generator;
+}
+
+void spawn_td_fuel(float fuel_size)
+{
+ if not(g_td)
+ {
+ remove(self);
+ return;
+ }
+ self.ammo_fuel = fuel_size * monster_skill;
+ StartItem("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Turret Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
+
+ self.velocity = randomvec() * 175 + '0 0 325';
+}
+
+void spawnfunc_td_waypoint()
+{
+ if not(g_td)
+ {
+ remove(self);
+ return;
+ }
+ string t1 = self.target;
+
+ self.classname = "td_waypoint";
+
+ if(self.target2 != "")
+ {
+ RandomSelection_Init();
+ RandomSelection_Add(world, 0, t1, 1, 1);
+ RandomSelection_Add(world, 0, self.target2, 1, 1);
+
+ self.target = RandomSelection_chosen_string;
+ }
+}
+
+void spawnfunc_monster_swarm()
+{
+ if not(g_td)
+ {
+ remove(self);
+ return;
+ }
+
+ string t1 = self.target;
+
+ switch(self.spawntype)
+ {
+ case SWARM_SWIM:
+ waterspawns_count += 1; break;
+ case SWARM_FLY:
+ flyspawns_count += 1; break;
+ default:
+ break;
+ }
+
+ switch(self.spawnflags)
+ {
+ case SWARM_STRONG:
+ self.classname = "swarm_strong"; break;
+ case SWARM_WEAK:
+ self.classname = "swarm_weak"; break;
+ default:
+ self.classname = "monster_swarm"; break;
+ }
+
+ if(self.target2 != "")
+ {
+ RandomSelection_Init();
+ RandomSelection_Add(world, 0, t1, 1, 1);
+ RandomSelection_Add(world, 0, self.target2, 1, 1);
+
+ self.target = RandomSelection_chosen_string;
+ }
+
+ WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '1 0.5 0');
+
+ if(self.target == "")
+ print("Warning: monster_swarm entity without a valid target\n");
+}
+
+void spawnturret(entity spawnedby, entity own, string turet, vector orig)
+{
+ if(spawnedby.classname != STR_PLAYER)
+ {
+ print("Warning: A non-player entity tried to spawn a turret\n");
+ return;
+ }
+
+ entity oldself;
+
+ oldself = self;
+ self = spawn();
+
+ setorigin(self, orig);
+ self.spawnflags = TSL_NO_RESPAWN;
+ self.monster_attack = TRUE;
+ self.realowner = own;
+ self.angles_y = spawnedby.v_angle_y;
+ spawnedby.turret_cnt += 1;
+ self.colormap = spawnedby.colormap;
+
+ switch(turet)
+ {
+ default:
+ case "plasma": spawnfunc_turret_plasma(); break;
+ case "mlrs": spawnfunc_turret_mlrs(); break;
+ case "phaser": spawnfunc_turret_phaser(); break;
+ case "hellion": spawnfunc_turret_hellion(); break;
+ case "walker": spawnfunc_turret_walker(); break;
+ case "flac": spawnfunc_turret_flac(); break;
+ case "tesla": spawnfunc_turret_tesla(); break;
+ case "fusionreactor": spawnfunc_turret_fusionreactor(); break;
+ }
+
+ self = oldself;
+}
+
+void buffturret (entity tur, float buff)
+{
+ tur.turret_buff += 1;
+ tur.max_health *= buff;
+ tur.tur_health = tur.max_health;
+ tur.health = tur.max_health;
+ tur.ammo_max *= buff;
+ tur.ammo_recharge *= buff;
+ tur.shot_dmg *= buff;
+ tur.shot_refire -= buff * 0.2;
+ tur.shot_radius *= buff;
+ tur.shot_speed *= buff;
+ tur.shot_spread *= buff;
+ tur.shot_force *= buff;
+}
+
+void AnnounceSpawn(string anounce)
+{
+ entity tail;
+ FOR_EACH_PLAYER(tail)
+ {
+ Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, strcat("^1A ", anounce, " has arrived!"), 0, 0);
+ }
+}
+
+entity PickSpawn (string strngth, string type)
+{
+ entity e;
+ RandomSelection_Init();
+ for(e = world;(e = find(e, classname, strngth)); )
+ {
+ RandomSelection_Add(e, 0, string_null, 1, 1);
+ }
+
+ return RandomSelection_chosen_ent;
+}
+
+void TD_SpawnMonster(string mnster, string strngth, string type)
+{
+ entity e, mon;
+
+ e = PickSpawn(strngth, type);
+
+ if(e == world)
+ e = PickSpawn("monster_swarm", "");
+
+ mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
+ mon.target = e.target;
+}
+
+string Monster_GetStrength(string mnster)
+{
+ switch(mnster)
+ {
+ case "knight":
+ case "wizard":
+ case "soldier":
+ case "enforcer":
+ case "zombie":
+ case "tarbaby":
+ case "dog":
+ case "spider":
+ case "fish":
+ return "swarm_weak";
+ case "ogre":
+ case "shambler":
+ case "shalrath":
+ case "hellknight":
+ case "demon":
+ return "swarm_strong";
+ default:
+ return "monster_swarm";
+ }
+}
+
+string Monster_GetType(string mnster)
+{
+ switch(mnster)
+ {
+ default:
+ case "knight":
+ case "soldier":
+ case "enforcer":
+ case "zombie":
+ case "spider":
+ case "tarbaby":
+ case "dog":
+ case "ogre":
+ case "shambler":
+ case "shalrath":
+ case "hellknight":
+ case "demon":
+ return "monster_swarm";
+ case "wizard":
+ return "monster_fly";
+ case "fish":
+ return "monster_swim";
+ }
+}
+
+string RandomMonster()
+{
+ RandomSelection_Init();
+
+ if(n_demons) RandomSelection_Add(world, 0, "demon", 1, 1);
+ if(n_shalraths) RandomSelection_Add(world, 0, "vore", 1, 1);
+ if(n_soldiers) RandomSelection_Add(world, 0, "soldier", 1, 1);
+ if(n_hknights) RandomSelection_Add(world, 0, "hellknight", 1, 1);
+ if(n_enforcers) RandomSelection_Add(world, 0, "enforcer", 1, 1);
+ if(n_zombies) RandomSelection_Add(world, 0, "zombie", 1, 1);
+ if(n_spiders) RandomSelection_Add(world, 0, "spider", 1, 1);
+ if(n_ogres) RandomSelection_Add(world, 0, "ogre", 1, 1);
+ if(n_dogs) RandomSelection_Add(world, 0, "dog", 1, 1);
+ if(n_knights) RandomSelection_Add(world, 0, "knight", 1, 1);
+ if(n_shamblers) RandomSelection_Add(world, 0, "shambler", 0.2, 0.2);
+ if(n_tarbabies) RandomSelection_Add(world, 0, "spawn", 0.2, 0.2);
+ if(n_wizards && flyspawns_count) RandomSelection_Add(world, 0, "scrag", 1, 1);
+ if(n_fish && waterspawns_count) RandomSelection_Add(world, 0, "fish", 0.2, 0.2);
+
+ return RandomSelection_chosen_string;
+}
+
+void combat_phase()
+{
+ string monstrngth, whichmon, montype;
+
+ current_phase = PHASE_COMBAT;
+
+ if(monster_count <= 0)
+ {
+ wave_end(FALSE);
+ return;
+ }
+
+ self.think = combat_phase;
+
+ whichmon = RandomMonster();
+
+ monstrngth = Monster_GetStrength(whichmon);
+ montype = Monster_GetType(whichmon);
+
+ if(current_monsters <= autocvar_g_td_current_monsters && whichmon != "")
+ {
+ TD_SpawnMonster(whichmon, monstrngth, montype);
+ self.nextthink = time + 3;
+ }
+ else
+ self.nextthink = time + 6;
+}
+
+void queue_monsters(float maxmonsters)
+{
+ float mc = 11; // note: shambler + tarbaby = 1
+
+ if(waterspawns_count > 0)
+ mc += 1;
+ if(flyspawns_count > 0)
+ mc += 1;
+
+ DistributeEvenly_Init(maxmonsters, mc);
+ n_demons = DistributeEvenly_Get(1);
+ n_ogres = DistributeEvenly_Get(1);
+ n_dogs = DistributeEvenly_Get(1);
+ n_knights = DistributeEvenly_Get(1);
+ n_shalraths = DistributeEvenly_Get(1);
+ n_soldiers = DistributeEvenly_Get(1);
+ n_hknights = DistributeEvenly_Get(1);
+ n_enforcers = DistributeEvenly_Get(1);
+ n_zombies = DistributeEvenly_Get(1);
+ n_spiders = DistributeEvenly_Get(1);
+ n_tarbabies = DistributeEvenly_Get(0.7);
+ n_shamblers = DistributeEvenly_Get(0.3);
+ if(flyspawns_count > 0)
+ n_wizards = DistributeEvenly_Get(1);
+ if(waterspawns_count > 0)
+ n_fish = DistributeEvenly_Get(1);
+}
+
+void combat_phase_begin()
+{
+ monster_count = totalmonsters;
+ entity head, tail;
+
+ print("^1Combat phase!\n");
+ FOR_EACH_PLAYER(tail)
+ {
+ Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "^1Combat phase!", 0, 0);
+ }
+ if(autocvar_sv_eventlog)
+ GameLogEcho(":combatphase");
+ self.think = combat_phase;
+ self.nextthink = time + 1;
+
+ for(head = world;(head = find(head, classname, "td_generator")); )
+ {
+ head.takedamage = DAMAGE_AIM;
+ }
+}
+
+float cphase_updates;
+void combat_phase_announce() // TODO: clean up these fail nextthinks...
+{
+ cphase_updates += 1;
+
+ if(cphase_updates == 0)
+ Announce("prepareforbattle");
+ else if(cphase_updates == 3)
+ Announce("3");
+ else if(cphase_updates == 4)
+ Announce("2");
+ else if(cphase_updates == 5)
+ Announce("1");
+ else if(cphase_updates == 6)
+ {
+ Announce("begin");
+ combat_phase_begin();
+ }
+
+ if(cphase_updates >= 6)
+ return;
+
+ self.think = combat_phase_announce;
+ self.nextthink = time + 1;
+}
+
+void build_phase()
+{
+ entity head;
+ float n_players = 0, gen_washealed = FALSE, player_washealed = FALSE;
+ string buildmsg, healmsg, countmsg, startmsg, genhealmsg;
+
+ current_phase = PHASE_BUILD;
+
+ for(head = world;(head = find(head, classname, "td_generator")); )
+ {
+ if(head.health <= 5 && head.max_health > 10)
+ Announce("lastsecond");
+
+ if(head.health < head.max_health)
+ {
+ gen_washealed = TRUE;
+ head.health = head.max_health;
+ WaypointSprite_UpdateHealth(head.sprite, head.health);
+ }
+ head.takedamage = DAMAGE_NO;
+ }
+
+ FOR_EACH_PLAYER(head)
+ {
+ if(head.health < 100)
+ {
+ player_washealed = TRUE;
+ break; // we found 1, so no need to check the others
+ }
+ }
+
+ totalmonsters += autocvar_g_td_monster_count_increment * wave_count;
+ monster_skill += autocvar_g_td_monsters_skill_increment;
+
+ if(wave_count < 1) wave_count = 1;
+
+ genhealmsg = (gen_washealed) ? ((td_gencount == 1) ? " and generator " : " and generators ") : "";
+ buildmsg = sprintf("%s build phase... ", (wave_count == max_waves) ? "^1Final wave^3" : sprintf("Wave %d", wave_count));
+ healmsg = (player_washealed) ? sprintf("All players %shealed. ", genhealmsg) : "";
+ countmsg = sprintf("Next monsters: %d. ", totalmonsters);
+ startmsg = sprintf("Wave starts in %d seconds", autocvar_g_td_buildphase_time);
+
+ FOR_EACH_PLAYER(head)
+ {
+ if(head.health < 100)
+ head.health = 100;
+
+ if(gen_washealed)
+ PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
+
+ n_players += 1;
+ Send_CSQC_Centerprint_Generic(head, CPID_KH_MSG, strcat(buildmsg, healmsg, countmsg, startmsg), 5, 0);
+ }
+
+ FOR_EACH_MONSTER(head)
+ {
+ if(head.health <= 0)
+ continue;
+ print(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
+ if(head.sprite)
+ WaypointSprite_Kill(head.sprite);
+ remove(head);
+ }
+
+ if(n_players >= 2)
+ {
+ totalmonsters += n_players;
+ monster_skill += n_players * 0.05;
+ }
+
+ if(monster_skill < 1) monster_skill = 1;
+
+ if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
+
+ monsters_total = totalmonsters;
+ monsters_killed = 0;
+
+ print(strcat(buildmsg, healmsg, countmsg, startmsg, "\n"));
+
+ queue_monsters(totalmonsters);
+
+ cphase_updates = -1;
+
+ if(autocvar_sv_eventlog)
+ GameLogEcho(sprintf(":buildphase:%d:%d", wave_count, totalmonsters));
+
+ self.think = combat_phase_announce;
+ self.nextthink = time + build_time - 6;
+}
+
+void wave_end(float starting)
+{
+ entity tail;
+ FOR_EACH_PLAYER(tail)
+ {
+ if(starting)
+ Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, "Defend the generator from waves of monsters!", 0, 0);
+ else
+ Send_CSQC_Centerprint_Generic(tail, CPID_KH_MSG, ((wave_count >= max_waves) ? "Level victory!" : "Wave victory!"), 0, 0);
+ }
+
+ if not(starting)
+ {
+ print((wave_count >= max_waves) ? "^2Level victory!\n" : "^2Wave victory!\n");
+ if(autocvar_sv_eventlog)
+ GameLogEcho(sprintf(":wave:%d:victory", wave_count));
+ }
+
+ if(wave_count >= max_waves)
+ {
+ gensurvived = TRUE;
+ return;
+ }
+
+ if(starting)
+ {
+ if(autocvar_g_td_monsters_skill_start)
+ monster_skill = autocvar_g_td_monsters_skill_start;
+ }
+ else
+ wave_count += 1;
+
+ self.think = build_phase;
+ self.nextthink = time + 3;
+}
+
+void td_ScoreRules()
+{
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_SCORE, "score", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_KILLS, "kills", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_TURKILLS, "frags", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_DEATHS, "deaths", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_SUICIDES, "suicides", SFL_LOWER_IS_BETTER | SFL_ALLOW_HIDE);
+ ScoreRules_basics_end();
+}
+
+void td_SpawnController()
+{
+ entity oldself = self;
+ self = spawn();
+ self.classname = "td_controller";
+ spawnfunc_td_controller();
+ self = oldself;
+}
+
+void td_DelayedInit()
+{
+ if(find(world, classname, "td_controller") == world)
+ {
+ print("No ""td_controller"" entity found on this map, creating it anyway.\n");
+ td_SpawnController();
+ }
+
+ td_ScoreRules();
+}
+
+void td_Init()
+{
+ InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
+{
+ if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ return TRUE;
+ if not(turret_target.flags & FL_MONSTER)
+ turret_target = world;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerThink)
+{
+ self.stat_current_wave = wave_count;
+ self.stat_totalwaves = max_waves;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
+{
+ self.bot_attack = FALSE;
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerDies)
+{
+ if(frag_attacker.flags & FL_MONSTER)
+ PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
+
+ if(frag_target == frag_attacker)
+ PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
+{
+ frag_score = 0;
+
+ return TRUE; // no frags counted in td
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerDamage_Calculate)
+{
+ if(frag_attacker.realowner == frag_target)
+ frag_damage = 0;
+
+ if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
+ frag_damage = 0;
+
+ if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
+ frag_damage = 0;
+
+ if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
+ frag_damage = 0;
+
+ if(!autocvar_g_td_pvp && frag_attacker != frag_target && frag_target.classname == STR_PLAYER && frag_attacker.classname == STR_PLAYER)
+ frag_damage = 0;
+
+ if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && frag_target.classname == STR_PLAYER)
+ frag_damage = 0;
+
+ if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
+ frag_damage = 0;
+
+ if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && frag_target.health <= 0)
+ {
+ // TODO: fix this? calling on damage may be unreliable
+ if(frag_target.realowner)
+ frag_target.realowner.turret_cnt -= 1;
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
+{
+ // No minibosses in tower defense
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterMove)
+{
+ entity player;
+ float n_players = 0;
+ FOR_EACH_PLAYER(player) { ++n_players; }
+
+ if(n_players < 1) // no players online, so do nothing
+ {
+ monster_target = world;
+ monster_speed_run = monster_speed_walk = 0;
+ return FALSE;
+ }
+
+ if((vlen(self.goalentity.origin - self.origin) <= 100 && self.goalentity.classname == "td_waypoint") || (vlen(self.goalentity.origin - self.origin) <= 200 && self.flags & FL_FLY && self.goalentity.classname == "td_waypoint"))
+ {
+ self.target = self.goalentity.target;
+ self.goalentity = find(world, targetname, self.target);
+ }
+
+ if(self.goalentity == world)
+ self.goalentity = PickGenerator();
+
+ monster_speed_run = 110 * monster_skill;
+ monster_speed_walk = 75 * monster_skill;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
+{
+ if(self.realowner && self.realowner.flags & FL_CLIENT)
+ {
+ sprint(self.realowner, "You can't spawn monsters in Tower Defense mode. Removed monster.\n");
+ if(self.sprite)
+ WaypointSprite_Kill(self.sprite);
+ remove(self);
+ return TRUE;
+ }
+
+ if(self.realowner == world) // nothing spawned it, so kill it
+ {
+ if(self.sprite)
+ WaypointSprite_Kill(self.sprite);
+ remove(self);
+ return TRUE;
+ }
+
+ self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
+
+ self.drop_size = self.health * 0.05;
+
+ if(self.drop_size < 1) self.drop_size = 1;
+
+ if(self.target) // follow target if available
+ self.goalentity = find(world, targetname, self.target);
+
+ self.origin += '0 0 25'; // hopefully this fixes monsters falling through the floor
+
+ switch(self.classname)
+ {
+ case "monster_knight": n_knights -= 1; break;
+ case "monster_dog": n_dogs -= 1; break;
+ case "monster_ogre": n_ogres -= 1; break;
+ case "monster_shambler": n_shamblers -= 1; AnnounceSpawn("Shambler"); break;
+ case "monster_wizard": n_wizards -= 1; break;
+ case "monster_shalrath": n_shalraths -= 1; break;
+ case "monster_soldier": n_soldiers -= 1; break;
+ case "monster_hellknight": n_hknights -= 1; break;
+ case "monster_enforcer": n_enforcers -= 1; break;
+ case "monster_demon": n_demons -= 1; break;
+ case "monster_zombie": n_zombies -= 1; break;
+ case "monster_spider": n_spiders -= 1; break;
+ case "monster_tarbaby": n_tarbabies -= 1; break;
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterDies)
+{
+ entity oldself;
+ vector backuporigin;
+
+ monster_count -= 1;
+ current_monsters -= 1;
+ monsters_killed += 1;
+
+ if(frag_attacker.classname == STR_PLAYER)
+ {
+ PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
+ PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
+ }
+ else if(frag_attacker.realowner.classname == STR_PLAYER)
+ {
+ PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
+ PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
+ }
+
+ backuporigin = self.origin;
+ oldself = self;
+ self = spawn();
+
+ self.gravity = 1;
+ setorigin(self, backuporigin + '0 0 5');
+ spawn_td_fuel(oldself.drop_size);
+ self.touch = M_Item_Touch;
+ if(self == world)
+ {
+ self = oldself;
+ return FALSE;
+ }
+ SUB_SetFade(self, time + 5, 1);
+
+ self = oldself;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
+{
+ float n_players = 0;
+ entity player;
+ local entity e;
+
+ FOR_EACH_PLAYER(player) { ++n_players; }
+
+ if(n_players < 1) // no players online, so do nothing
+ {
+ self.enemy = world;
+ return TRUE;
+ }
+
+ for(e = world;(e = findflags(e, monster_attack, TRUE)); )
+ {
+ if(monster_isvalidtarget(e, self, FALSE))
+ if((vlen(trace_endpos - self.origin) < 100 && e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (vlen(trace_endpos - self.origin) < 200 && e.classname != "td_generator") || (vlen(trace_endpos - self.origin) < 500 && e.classname == "td_generator"))
+ {
+ self.enemy = e;
+ }
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_SetStartItems)
+{
+ start_ammo_fuel = 150; // to be nice...
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretSpawn)
+{
+ if(self.realowner == world)
+ return TRUE; // wasn't spawned by a player
+
+ self.bot_attack = FALSE;
+ self.turret_buff = 1;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_DisableVehicles)
+{
+ // you shall not spawn!
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerCommand)
+{
+ if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
+ makevectors(self.v_angle);
+ WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+ if(cmd_name == "turretspawn")
+ {
+ if(argv(1) == "list")
+ {
+ sprint(self, "Available turrets:\n");
+ sprint(self, "^3mlrs walker plasma towerbuff\n");
+ return TRUE;
+ }
+ if(self.classname != STR_PLAYER || self.health <= 0)
+ {
+ sprint(self, "Can't spawn turrets while spectating/dead\n");
+ return TRUE;
+ }
+ if(self.turret_cnt >= max_turrets)
+ {
+ sprint(self, strcat("Can't spawn more than ", ftos(max_turrets), " turrets\n"));
+ return TRUE;
+ }
+ switch(argv(1))
+ {
+ case "plasma":
+ {
+ if(self.ammo_fuel < autocvar_g_td_turret_plasma_cost) break;
+ self.ammo_fuel -= autocvar_g_td_turret_plasma_cost;
+ spawnturret(self, self, "plasma", trace_endpos);
+ sprint(self, "Spawned 1 plasma turret", "\n");
+ return TRUE;
+ }
+ case "mlrs":
+ {
+ if(self.ammo_fuel < autocvar_g_td_turret_mlrs_cost) break;
+ self.ammo_fuel -= autocvar_g_td_turret_mlrs_cost;
+ spawnturret(self, self, "mlrs", trace_endpos);
+ sprint(self, "Spawned 1 MLRS turret", "\n");
+ return TRUE;
+ }
+ case "walker":
+ {
+ if(self.ammo_fuel < autocvar_g_td_turret_walker_cost) break;
+ self.ammo_fuel -= autocvar_g_td_turret_walker_cost;
+ spawnturret(self, self, "walker", trace_endpos);
+ sprint(self, "Spawned 1 walker turret", "\n");
+ return TRUE;
+ }
+ case "towerbuff":
+ {
+ if(self.ammo_fuel < autocvar_g_td_tower_buff_cost) break;
+ self.ammo_fuel -= autocvar_g_td_tower_buff_cost;
+ spawnturret(self, self, "fusionreactor", trace_endpos);
+ sprint(self, "Spawned 1 tower buff turret\n");
+ return TRUE;
+ }
+ default:
+ {
+ sprint(self, "Invalid turret. type 'cmd turret list' to see a list of all available turrets.\n");
+ return TRUE;
+ }
+ }
+ sprint(self, strcat("You do not have enough fuel to spawn a ", argv(1), " turret\n"));
+ return TRUE;
+ }
+ if(cmd_name == "buffturret")
+ {
+ if(trace_ent.realowner != self || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+ {
+ sprint(self, "You need to aim at your turret to upgrade it\n");
+ return TRUE;
+ }
+ if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)
+ {
+ sprint(self, strcat("You need ", ftos(autocvar_g_td_turret_upgrade_cost), " fuel to increase this turret's power\n"));
+ return TRUE;
+ }
+ if(trace_ent.turret_buff >= 3)
+ {
+ sprint(self, "This turret cannot be buffed up any higher\n");
+ return TRUE;
+ }
+
+ self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
+ trace_ent.SendFlags |= TNSF_STATUS;
+ buffturret(trace_ent, 1.2);
+ sprint(self, "Turret power increased by 20%!\n");
+
+ return TRUE;
+ }
+ if(cmd_name == "turretremove")
+ {
+ if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && trace_ent.realowner == self)
+ {
+ self.turret_cnt -= 1;
+ sprint(self, strcat("You removed your ", trace_ent.netname, "\n"));
+ remove(trace_ent.tur_head);
+ remove(trace_ent);
+ return TRUE;
+ }
+ sprint(self, "You need to aim at your turret to remove it\n");
+ return TRUE;
+ }
+ if(cmd_name == "debugmonsters")
+ {
+ sprint(self, strcat("^3Current wave: ^1", ftos(wave_count), "\n"));
+ sprint(self, strcat("^3Maximum waves: ^1", ftos(max_waves), "\n"));
+ sprint(self, strcat("^3Monster skill: ^1", ftos(monster_skill), "\n"));
+ sprint(self, strcat("^3Current monsters: ^1", ftos(monster_count), "\n"));
+ sprint(self, strcat("^3Maximum monsters: ^1", ftos(totalmonsters), "\n"));
+ sprint(self, strcat("^3Current ogres: ^1", ftos(n_ogres), "\n"));
+ sprint(self, strcat("^3Current knights: ^1", ftos(n_knights), "\n"));
+ sprint(self, strcat("^3Current dogs: ^1", ftos(n_dogs), "\n"));
+ sprint(self, strcat("^3Current shamblers: ^1", ftos(n_shamblers), "\n"));
+ sprint(self, strcat("^3Current scrags: ^1", ftos(n_wizards), "\n"));
+ sprint(self, strcat("^3Current vores: ^1", ftos(n_shalraths), "\n"));
+ sprint(self, strcat("^3Current grunts: ^1", ftos(n_soldiers), "\n"));
+ sprint(self, strcat("^3Current hell knights: ^1", ftos(n_hknights), "\n"));
+ sprint(self, strcat("^3Current enforcers: ^1", ftos(n_enforcers), "\n"));
+ sprint(self, strcat("^3Current fiends: ^1", ftos(n_demons), "\n"));
+ sprint(self, strcat("^3Current zombies: ^1", ftos(n_zombies), "\n"));
+ sprint(self, strcat("^3Current spawns: ^1", ftos(n_tarbabies), "\n"));
+ sprint(self, strcat("^3Current rotfish: ^1", ftos(n_fish), "\n"));
+ sprint(self, strcat("^3Current spiders: ^1", ftos(n_spiders), "\n"));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+MUTATOR_DEFINITION(gamemode_td)
+{
+ MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterMove, td_MonsterMove, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterCheckBossFlag, td_MonsterCheckBossFlag, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SetStartItems, td_SetStartItems, CBC_ORDER_ANY);
+ MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
+ MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(GiveFragsForKill, td_GiveFragsForKill, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPreThink, td_PlayerThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDies, td_PlayerDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDamage_Calculate, td_PlayerDamage_Calculate, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
+
+ MUTATOR_ONADD
+ {
+ if(time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ cvar_settemp("g_monsters", "1");
+ cvar_settemp("g_turrets", "1");
+ td_Init();
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ error("This is a game type and it cannot be removed at runtime.");
+ }
+
+ return FALSE;
+}
--- /dev/null
+// Counters
+float monster_count, totalmonsters;
+float n_knights, n_dogs, n_ogres, n_shamblers, n_wizards, n_shalraths, n_soldiers, n_hknights, n_enforcers, n_demons, n_zombies, n_tarbabies, n_fish, n_spiders;
+float current_monsters;
+float waterspawns_count, flyspawns_count;
+float wave_count, max_waves;
+float max_turrets;
+
+// Monster defs
+.float drop_size;
+
+// Turret defs
+.float turret_buff;
+
+// TD defs
+.float stat_current_wave;
+.float stat_totalwaves;
+.float spawntype;
+float SWARM_NORMAL = 0;
+float SWARM_WEAK = 1;
+float SWARM_STRONG = 2;
+float SWARM_FLY = 3;
+float SWARM_SWIM = 4;
+float build_time;
+float td_dont_end;
+void(float starting) wave_end;
+.float turret_cnt;
+float td_gencount;
+void() spawnfunc_td_controller;
+float current_phase;
+#define PHASE_BUILD 1
+#define PHASE_COMBAT 2
+
+// Scores
+#define SP_TD_KILLS 0
+#define SP_TD_TURKILLS 2
+#define SP_TD_SCORE 4
+#define SP_TD_DEATHS 6
+#define SP_TD_SUICIDES 8
+
+// Controller
+.float maxwaves;
+.float monstercount;
+.float startwave;
+.float dontend;
+.float maxturrets;
+.float buildtime;
+
+// Generator
+float gendestroyed;
+#define GENERATOR_MIN '-52 -52 -14'
+#define GENERATOR_MAX '52 52 75'
\ No newline at end of file
--- /dev/null
+// Zombie Apocalypse mutator - small side project
+// Spawns a defined number of zombies at the start of a match
+
+float za_numspawns;
+entity PickZombieSpawn()
+{
+ entity sp;
+
+ RandomSelection_Init();
+
+ if(teamplay)
+ {
+ for(sp = world; (sp = find(sp, classname, "info_player_team1")); )
+ {
+ RandomSelection_Add(sp, 0, string_null, 1, 1);
+ }
+ }
+ else
+ {
+ for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
+ {
+ RandomSelection_Add(sp, 0, string_null, 1, 1);
+ }
+ }
+
+ return RandomSelection_chosen_ent;
+}
+
+void zombie_spawn_somewhere ()
+{
+ if(gameover) { return; }
+
+ entity mon, sp;
+
+ if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ {
+ mon = spawnmonster("zombie", self, self, self.origin, TRUE, 2);
+ tracebox(mon.origin, mon.mins, mon.maxs, mon.origin, MOVE_NOMONSTERS, mon);
+
+ if(trace_startsolid)
+ {
+ sp = PickZombieSpawn();
+ if(sp)
+ setorigin(mon, sp.origin);
+ }
+
+ za_numspawns += 1;
+ }
+ else
+ zombie_spawn_somewhere();
+}
+
+void spawn_zombies ()
+{
+ float numzoms;
+ entity e;
+
+ print("Them zombies be spawnin'!\n");
+
+ numzoms = autocvar_g_za_monster_count;
+
+ while(numzoms > 0)
+ {
+ e = spawn();
+ e.think = zombie_spawn_somewhere;
+ e.nextthink = time;
+
+ numzoms -= 1;
+ }
+
+ if(self)
+ remove(self);
+}
+
+void za_init ()
+{
+ entity e;
+
+ e = spawn();
+ e.think = spawn_zombies;
+ e.nextthink = time + 3;
+}
+
+MUTATOR_HOOKFUNCTION(Zombies_BuildMutatorsString)
+{
+ ret_string = strcat(ret_string, ":Zombies");
+ return 0;
+}
+
+MUTATOR_HOOKFUNCTION(Zombies_BuildMutatorsPrettyString)
+{
+ ret_string = strcat(ret_string, ", Zombies");
+ return 0;
+}
+
+MUTATOR_DEFINITION(mutator_zombie_apocalypse)
+{
+ MUTATOR_HOOK(BuildMutatorsString, Zombies_BuildMutatorsString, CBC_ORDER_ANY);
+ MUTATOR_HOOK(BuildMutatorsPrettyString, Zombies_BuildMutatorsPrettyString, CBC_ORDER_ANY);
+
+ MUTATOR_ONADD
+ {
+ za_init();
+ }
+
+ return 0;
+}
MUTATOR_DECLARATION(gamemode_ctf);
MUTATOR_DECLARATION(gamemode_nexball);
MUTATOR_DECLARATION(gamemode_onslaught);
+MUTATOR_DECLARATION(gamemode_td);
MUTATOR_DECLARATION(gamemode_domination);
MUTATOR_DECLARATION(mutator_dodging);
MUTATOR_DECLARATION(mutator_physical_items);
MUTATOR_DECLARATION(mutator_vampire);
MUTATOR_DECLARATION(mutator_superspec);
+MUTATOR_DECLARATION(mutator_zombie_apocalypse);
MUTATOR_DECLARATION(sandbox);
mutators/gamemode_keepaway.qh
mutators/gamemode_nexball.qh
mutators/mutator_dodging.qh
+mutators/gamemode_td.qh
//// tZork Turrets ////
tturrets/include/turrets_early.qh
../common/explosion_equation.qc
+monsters/monsters.qh
+
mutators/base.qc
mutators/gamemode_ctf.qc
mutators/gamemode_domination.qc
mutators/gamemode_keepaway.qc
mutators/gamemode_nexball.qc
mutators/gamemode_onslaught.qc
+mutators/gamemode_td.qc
mutators/mutator_invincibleproj.qc
mutators/mutator_new_toys.qc
mutators/mutator_nix.qc
mutators/mutator_physical_items.qc
mutators/sandbox.qc
mutators/mutator_superspec.qc
+mutators/mutator_zombie_apocalypse.qc
../warpzonelib/anglestransform.qc
../warpzonelib/mathlib.qc
float vehic = (self.vehicle_flags & VHF_ISVEHICLE);
float projectile = (self.flags & FL_PROJECTILE);
+ float monster = (self.flags & FL_MONSTER);
if (self.watertype <= CONTENT_WATER && self.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug)
{
self.dmgtime = 0;
}
- if(!vehic && !projectile) // vehicles and projectiles don't drown
+ if(!vehic && !projectile && !monster) // vehicles, monsters and projectiles don't drown
{
if (self.waterlevel != WATERLEVEL_SUBMERGED)
{
MUTATOR_ADD(gamemode_ctf);
have_team_spawns = -1; // request team spawns
}
+
+ if(g_td)
+ {
+ fraglimit_override = 0; // no limits in TD - it's a survival mode
+ leadlimit_override = 0;
+ MUTATOR_ADD(gamemode_td);
+ }
if(g_runematch)
{
self.tur_head.avelocity = self.avelocity;
self.tur_head.angles = self.idle_aim;
self.health = self.tur_health;
+ self.max_health = self.tur_health;
self.enemy = world;
self.volly_counter = self.shot_volly;
float turret_validate_target(entity e_turret, entity e_target, float validate_flags)
{
vector v_tmp;
+
+ turret_target = e_target;
+ turret = e_turret;
+ if(MUTATOR_CALLHOOK(TurretValidateTarget))
+ return 1;
+ e_target = turret_target;
+ e_turret = turret;
//if(!validate_flags & TFL_TARGETSELECT_NOBUILTIN)
// return -0.5;
if not (self.health)
self.health = 1000;
self.tur_health = max(1, self.health);
+ self.max_health = self.tur_health;
+ self.bot_attack = TRUE;
+ self.monster_attack = TRUE;
if not (self.turrcaps_flags)
self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL;
self.turret_score_target = turret_stdproc_targetscore_generic;
self.use = turret_stdproc_use;
- self.bot_attack = TRUE;
++turret_count;
self.nextthink = time + 1;
if ((_turret.target_select_missilebias > 0) && (_target.flags & FL_PROJECTILE))
m_score = 1;
- if ((_turret.target_select_playerbias > 0) && (_target.flags & FL_CLIENT))
+ if ((_turret.target_select_playerbias > 0) && (_target.flags & FL_CLIENT) && !g_td)
+ p_score = 1;
+
+ if(g_td && _target.flags & FL_MONSTER)
p_score = 1;
d_score = max(d_score, 0);
vector fl_org;
self.enemy.ammo = min(self.enemy.ammo + self.shot_dmg,self.enemy.ammo_max);
+ if(g_td) // auto repair?
+ {
+ self.enemy.health = min(self.enemy.health + self.shot_dmg,self.enemy.max_health);
+ self.enemy.tur_health = min(self.enemy.tur_health + self.shot_dmg,self.enemy.max_health);
+ }
fl_org = 0.5 * (self.enemy.absmin + self.enemy.absmax);
te_smallflash(fl_org);
}
return 0;
if (self.ammo < self.shot_dmg)
- return 0;
+ return 0;
+
+ if (vlen(self.enemy.origin - self.origin) > self.target_range)
+ return 0;
+ if(g_td)
+ {
+ if(self.realowner != self.enemy.realowner)
+ return 0;
+
+ if(self.enemy.turrcaps_flags & TFL_TURRCAPS_AMMOSOURCE)
+ return 0;
+
+ if(self.enemy.health >= self.enemy.max_health)
+ return 0;
+ }
+
if (self.enemy.ammo >= self.enemy.ammo_max)
return 0;
- if (vlen(self.enemy.origin - self.origin) > self.target_range)
- return 0;
-
- if(self.team != self.enemy.team)
+ if(teamplay && self.team != self.enemy.team)
return 0;
if not (self.enemy.ammo_flags & TFL_AMMO_ENERGY)
m_speed = vlen(self.velocity);
// Enemy dead? just keep on the current heading then.
- if (self.enemy == world || self.enemy.deadflag != DEAD_NO)
+ if (self.enemy == world || self.enemy.deadflag != DEAD_NO || (g_td && !(self.enemy.flags & FL_MONSTER || self.enemy.classname == "td_generator")) || self.enemy.classname == "td_generator")
self.enemy = world;
if (self.enemy)
if(e.classname == "player")
return TRUE;
- if(e.classname == "monster_zombie")
+ if(e.flags & FL_MONSTER)
return TRUE;
return FALSE;
self.pos1 = self.origin;
self.pos2 = self.angles;
self.tur_head.team = self.team;
+
+ if(MUTATOR_CALLHOOK(VehicleSpawn))
+ return FALSE;
return TRUE;
}
return;
}
- if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen)
+ if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen || owner_player.frozen)
{
if(self == owner_player.lgbeam)
owner_player.lgbeam = world;
--- /dev/null
+set g_za 0 "Enable zombie apocalypse mutator"
+set g_za_monster_count 20
\ No newline at end of file