]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Fri, 22 Mar 2013 06:23:47 +0000 (17:23 +1100)
committerMario <mario.mario@y7mail.com>
Fri, 22 Mar 2013 06:23:47 +0000 (17:23 +1100)
124 files changed:
commands.cfg
defaultXonotic.cfg
gamemodes.cfg
models/monsters/demon.mdl [new file with mode: 0644]
models/monsters/demon.mdl.framegroups [new file with mode: 0644]
models/monsters/demon.txt [new file with mode: 0644]
models/monsters/dog.dpm [new file with mode: 0644]
models/monsters/dog.dpm.framegroups [new file with mode: 0644]
models/monsters/dog.txt [new file with mode: 0644]
models/monsters/fish.mdl [new file with mode: 0644]
models/monsters/fish.mdl.framegroups [new file with mode: 0644]
models/monsters/gpl.txt [new file with mode: 0644]
models/monsters/hknight.mdl [new file with mode: 0644]
models/monsters/hknight.mdl.framegroups [new file with mode: 0644]
models/monsters/hknight.txt [new file with mode: 0644]
models/monsters/knight.mdl [new file with mode: 0644]
models/monsters/knight.mdl.framegroups [new file with mode: 0644]
models/monsters/knight.txt [new file with mode: 0644]
models/monsters/mage.dpm [new file with mode: 0644]
models/monsters/mage.dpm.framegroups [new file with mode: 0644]
models/monsters/monsters.txt [new file with mode: 0644]
models/monsters/ogre.mdl [new file with mode: 0644]
models/monsters/ogre.mdl.framegroups [new file with mode: 0644]
models/monsters/shambler.mdl [new file with mode: 0644]
models/monsters/shambler.mdl.framegroups [new file with mode: 0644]
models/monsters/soldier.zym [new file with mode: 0644]
models/monsters/spider.dpm [new file with mode: 0644]
models/monsters/spider.dpm.framegroups [new file with mode: 0644]
models/monsters/spider.txt [new file with mode: 0644]
models/monsters/tarbaby.mdl [new file with mode: 0644]
models/monsters/tarbaby.mdl.framegroups [new file with mode: 0644]
models/monsters/wizard.mdl [new file with mode: 0644]
models/monsters/wizard.mdl.framegroups [new file with mode: 0644]
models/monsters/wizard.txt [new file with mode: 0644]
models/monsters/zombie.dpm_0.skin [new file with mode: 0644]
models/monsters/zombie.dpm_1.skin [new file with mode: 0644]
models/monsters/zombie.dpm_2.skin [new file with mode: 0644]
models/td/barricade.md3 [new file with mode: 0644]
monster_zombie.cfg [deleted file]
monsters.cfg [new file with mode: 0644]
qcsrc/client/projectile.qc
qcsrc/client/scoreboard.qc
qcsrc/common/constants.qh
qcsrc/common/deathtypes.qh
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/notifications.qh
qcsrc/menu/classes.c
qcsrc/menu/xonotic/dialog_monstertools.c [new file with mode: 0644]
qcsrc/menu/xonotic/dialog_towerdefense.c [new file with mode: 0644]
qcsrc/menu/xonotic/mainwindow.c
qcsrc/server/accuracy.qc
qcsrc/server/autocvars.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/cl_weaponsystem.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/cmd.qh
qcsrc/server/command/sv_cmd.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_hook.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/monsters/lib/defs.qh [new file with mode: 0644]
qcsrc/server/monsters/lib/monsters.qc [new file with mode: 0644]
qcsrc/server/monsters/lib/spawn.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/demon.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/dog.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/enforcer.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/fish.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/hknight.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/knight.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/ogre.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/shalrath.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/shambler.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/soldier.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/spawner.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/spider.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/tarbaby.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/wizard.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/zombie.qc [new file with mode: 0644]
qcsrc/server/monsters/monsters.qh [new file with mode: 0644]
qcsrc/server/movelib.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/gamemode_rts.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_rts.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_towerdefense.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_towerdefense.qh [new file with mode: 0644]
qcsrc/server/mutators/mutator_zombie_apocalypse.qc [new file with mode: 0644]
qcsrc/server/mutators/mutators.qh
qcsrc/server/progs.src
qcsrc/server/sv_main.qc
qcsrc/server/teamplay.qc
qcsrc/server/tturrets/system/system_damage.qc
qcsrc/server/tturrets/system/system_main.qc
qcsrc/server/tturrets/system/system_scoreprocs.qc
qcsrc/server/tturrets/units/unit_fusionreactor.qc
qcsrc/server/tturrets/units/unit_walker.qc
qcsrc/server/vehicles/vehicles.qc
qcsrc/server/w_electro.qc
scripts/barricade.shader [new file with mode: 0644]
scripts/mage.shader [new file with mode: 0644]
scripts/shaderlist.txt
sound/monsters/zombie_death.ogg [new file with mode: 0644]
sound/monsters/zombie_idle.ogg [new file with mode: 0644]
sound/monsters/zombie_sight.ogg [new file with mode: 0644]
textures/barricade.tga [new file with mode: 0644]
textures/barricade_norm.tga [new file with mode: 0644]
textures/bloodyskull_pants.jpg [new file with mode: 0644]
textures/cerberus/cerberus_text.png [new file with mode: 0644]
textures/cerberus/cerberus_text_pants.png [new file with mode: 0644]
textures/mage.tga [new file with mode: 0644]
textures/marine.tga [new file with mode: 0644]
textures/marine_gloss.tga [new file with mode: 0644]
textures/marine_glow.tga [new file with mode: 0644]
textures/marine_norm.tga [new file with mode: 0644]
textures/marine_pants.tga [new file with mode: 0644]
textures/marine_shirt.tga [new file with mode: 0644]
textures/meat_pants.tga [new file with mode: 0644]
textures/spider/spidertex.tga [new file with mode: 0644]
za.cfg [new file with mode: 0644]

index 70ee53ecb027ade180ba8dfe4855c0e9c4709d2e..a01ffc9c69084dae30932315f090359fc954d1c2 100644 (file)
@@ -104,6 +104,7 @@ alias menu_showhudexit "menu_cmd directmenu HUDExit"
 alias menu_showhudoptions "menu_cmd directpanelhudmenu ${* ?}"
 alias menu_showsandboxtools "menu_cmd directmenu SandboxTools"
 alias menu_showquitdialog "menu_cmd directmenu Quit"
+alias menu_showtdtools "menu_cmd directmenu TowerDefense"
 
 // command executed before loading a map by the menu
 // makes sure maxplayers is at least minplayers or bot_number + 1
@@ -158,6 +159,9 @@ alias reportcvar           "qc_cmd_cmd    reportcvar           ${* ?}" // Old sy
 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 editmob                     "qc_cmd_cmd    mobedit                          ${* ?}" // Edit a monster's properties
+alias killmob                     "qc_cmd_cmd    mobkill                          ${* ?}" // Kill a monster
+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
@@ -176,6 +180,11 @@ alias spec "spectate"
 
 // mutator aliases
 alias sandbox "cmd g_sandbox ${* ?}"
+alias spawnturret "cmd turretspawn ${* ?}"
+alias debugmonsters "cmd debugmonsters ${* ?}"
+alias upgradeturret "cmd buffturret ${* ?}"
+alias rmturret "cmd turretremove ${* ?}"
+alias repairturret "cmd repairturret ${* ?}"
 
 
 // ============================================================
@@ -187,6 +196,7 @@ alias allspec              "qc_cmd_sv     allspec              ${* ?}" // Force
 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
index 40730b21c237fe631dfd25af6b99dbb750519773..27558826273719777d492ea379ab903739128cdc 100644 (file)
@@ -442,6 +442,14 @@ seta menu_sandbox_edit_material ""
 
 bind f7 menu_showsandboxtools
 
+seta menu_monsters_edit_spawn ""
+seta menu_monsters_edit_name ""
+seta menu_monsters_edit_skin 0
+seta menu_monsters_edit_color ""
+seta menu_monsters_edit_movetarget 1
+
+seta menu_td_edit_spawn ""
+
 set g_playerclip_collisions 1 "0 = disable collision testing against playerclips, might be useful on some defrag maps"
 set g_botclip_collisions 1 "0 = disable collision testing against botclips, might be useful on some defrag maps"
 
@@ -1561,6 +1569,8 @@ exec vehicles.cfg
 exec crosshairs.cfg
 exec gamemodes.cfg
 exec notifications.cfg
+exec monsters.cfg
+exec za.cfg
 
 // load console command aliases and settings
 exec commands.cfg
index af8d3e83d825411b18c8b58356711a49a46be44e..d9705ad9663c522a308a502b8341912a2ae0d882 100644 (file)
@@ -37,6 +37,8 @@ alias cl_hook_gamestart_nb
 alias cl_hook_gamestart_cts
 alias cl_hook_gamestart_ka
 alias cl_hook_gamestart_ft
+alias cl_hook_gamestart_td
+alias cl_hook_gamestart_rts
 alias cl_hook_gameend
 alias cl_hook_activeweapon
 
@@ -58,6 +60,8 @@ alias sv_hook_gamestart_nb
 alias sv_hook_gamestart_cts
 alias sv_hook_gamestart_ka
 alias sv_hook_gamestart_ft
+alias sv_hook_gamestart_td
+alias sv_hook_gamestart_rts
 alias sv_hook_gamerestart
 alias sv_hook_gameend
 
@@ -134,6 +138,12 @@ set g_cts_weapon_stay 2
 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_rts_respawn_waves 0
+set g_rts_respawn_delay 0
+set g_rts_weapon_stay 0
 
 
 // =======
@@ -410,3 +420,43 @@ set g_race 0 "Race: be faster than your opponents"
 set g_race_qualifying_timelimit 0
 set g_race_qualifying_timelimit_override -1
 set g_race_teams 0     "when 2, 3, or 4, the race is played as a team game (the team members can add up their laps)"
+
+// ===============
+//  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_monsters_ignore_turrets 0
+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_turret_towerbuff_cost 70
+set g_td_turret_barricade_cost 20
+set g_td_turret_flac_cost 40
+set g_td_turret_upgrade_cost 100
+set g_td_turret_repair_cost 20
+set g_td_barricade_damage 10
+set g_td_monsters_speed_walk 75
+set g_td_monsters_speed_run 110
+set g_td_monsters_spawn_delay 3
+
+// ====================
+//  real-time strategy
+// ====================
+set g_rts 0 "Real-Time Strategy: defend your generator & turrets by controlling monsters"
diff --git a/models/monsters/demon.mdl b/models/monsters/demon.mdl
new file mode 100644 (file)
index 0000000..d2a566b
Binary files /dev/null and b/models/monsters/demon.mdl differ
diff --git a/models/monsters/demon.mdl.framegroups b/models/monsters/demon.mdl.framegroups
new file mode 100644 (file)
index 0000000..b87a087
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/demon.txt b/models/monsters/demon.txt
new file mode 100644 (file)
index 0000000..fa60336
--- /dev/null
@@ -0,0 +1,25 @@
+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
diff --git a/models/monsters/dog.dpm b/models/monsters/dog.dpm
new file mode 100644 (file)
index 0000000..e220158
Binary files /dev/null and b/models/monsters/dog.dpm differ
diff --git a/models/monsters/dog.dpm.framegroups b/models/monsters/dog.dpm.framegroups
new file mode 100644 (file)
index 0000000..9ace1c4
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+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
diff --git a/models/monsters/dog.txt b/models/monsters/dog.txt
new file mode 100644 (file)
index 0000000..4cd5dde
--- /dev/null
@@ -0,0 +1 @@
+This cerberus model is GPL 2.0 compatible (http://opengameart.org/content/animated-cerberus).
\ No newline at end of file
diff --git a/models/monsters/fish.mdl b/models/monsters/fish.mdl
new file mode 100644 (file)
index 0000000..e1db999
Binary files /dev/null and b/models/monsters/fish.mdl differ
diff --git a/models/monsters/fish.mdl.framegroups b/models/monsters/fish.mdl.framegroups
new file mode 100644 (file)
index 0000000..bf18b81
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/gpl.txt b/models/monsters/gpl.txt
new file mode 100644 (file)
index 0000000..6bf61e7
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/hknight.mdl b/models/monsters/hknight.mdl
new file mode 100644 (file)
index 0000000..a0cd4d7
Binary files /dev/null and b/models/monsters/hknight.mdl differ
diff --git a/models/monsters/hknight.mdl.framegroups b/models/monsters/hknight.mdl.framegroups
new file mode 100644 (file)
index 0000000..cc16b30
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/hknight.txt b/models/monsters/hknight.txt
new file mode 100644 (file)
index 0000000..1dffdb7
--- /dev/null
@@ -0,0 +1,25 @@
+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
diff --git a/models/monsters/knight.mdl b/models/monsters/knight.mdl
new file mode 100644 (file)
index 0000000..36ebd61
Binary files /dev/null and b/models/monsters/knight.mdl differ
diff --git a/models/monsters/knight.mdl.framegroups b/models/monsters/knight.mdl.framegroups
new file mode 100644 (file)
index 0000000..c4acc5e
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/knight.txt b/models/monsters/knight.txt
new file mode 100644 (file)
index 0000000..59cec6c
--- /dev/null
@@ -0,0 +1,25 @@
+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
diff --git a/models/monsters/mage.dpm b/models/monsters/mage.dpm
new file mode 100644 (file)
index 0000000..3686b74
Binary files /dev/null and b/models/monsters/mage.dpm differ
diff --git a/models/monsters/mage.dpm.framegroups b/models/monsters/mage.dpm.framegroups
new file mode 100644 (file)
index 0000000..4a72c65
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+Generated framegroups file for mage
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 31 30 1 // mage idle
+32 31 30 1 // mage walk
+63 16 30 1 // mage attack
+79 16 30 1 // mage hit
+95 31 30 0 // mage die
+126 31 60 1 // mage walk
diff --git a/models/monsters/monsters.txt b/models/monsters/monsters.txt
new file mode 100644 (file)
index 0000000..9e55cb8
--- /dev/null
@@ -0,0 +1,31 @@
+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
diff --git a/models/monsters/ogre.mdl b/models/monsters/ogre.mdl
new file mode 100644 (file)
index 0000000..a0cd4d7
Binary files /dev/null and b/models/monsters/ogre.mdl differ
diff --git a/models/monsters/ogre.mdl.framegroups b/models/monsters/ogre.mdl.framegroups
new file mode 100644 (file)
index 0000000..0d69386
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/shambler.mdl b/models/monsters/shambler.mdl
new file mode 100644 (file)
index 0000000..2e23dfa
Binary files /dev/null and b/models/monsters/shambler.mdl differ
diff --git a/models/monsters/shambler.mdl.framegroups b/models/monsters/shambler.mdl.framegroups
new file mode 100644 (file)
index 0000000..b003d56
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/soldier.zym b/models/monsters/soldier.zym
new file mode 100644 (file)
index 0000000..1615af4
Binary files /dev/null and b/models/monsters/soldier.zym differ
diff --git a/models/monsters/spider.dpm b/models/monsters/spider.dpm
new file mode 100644 (file)
index 0000000..d0bc9ce
Binary files /dev/null and b/models/monsters/spider.dpm differ
diff --git a/models/monsters/spider.dpm.framegroups b/models/monsters/spider.dpm.framegroups
new file mode 100644 (file)
index 0000000..039a7c3
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+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
diff --git a/models/monsters/spider.txt b/models/monsters/spider.txt
new file mode 100644 (file)
index 0000000..7d7cdaf
--- /dev/null
@@ -0,0 +1 @@
+This spider model is GPL 2.0 compatible (http://opengameart.org/content/low-poly-spider-glest).
\ No newline at end of file
diff --git a/models/monsters/tarbaby.mdl b/models/monsters/tarbaby.mdl
new file mode 100644 (file)
index 0000000..db8c318
Binary files /dev/null and b/models/monsters/tarbaby.mdl differ
diff --git a/models/monsters/tarbaby.mdl.framegroups b/models/monsters/tarbaby.mdl.framegroups
new file mode 100644 (file)
index 0000000..7271df4
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/wizard.mdl b/models/monsters/wizard.mdl
new file mode 100644 (file)
index 0000000..b130ba0
Binary files /dev/null and b/models/monsters/wizard.mdl differ
diff --git a/models/monsters/wizard.mdl.framegroups b/models/monsters/wizard.mdl.framegroups
new file mode 100644 (file)
index 0000000..c51109a
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/models/monsters/wizard.txt b/models/monsters/wizard.txt
new file mode 100644 (file)
index 0000000..3b00dde
--- /dev/null
@@ -0,0 +1,31 @@
+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
diff --git a/models/monsters/zombie.dpm_0.skin b/models/monsters/zombie.dpm_0.skin
new file mode 100644 (file)
index 0000000..19ad7a7
--- /dev/null
@@ -0,0 +1,2 @@
+bloodyskull,bloodyskull
+meat,meat
\ No newline at end of file
diff --git a/models/monsters/zombie.dpm_1.skin b/models/monsters/zombie.dpm_1.skin
new file mode 100644 (file)
index 0000000..1561520
--- /dev/null
@@ -0,0 +1,2 @@
+bloodyskull,bloodyskull_alien
+meat,meat_alien
\ No newline at end of file
diff --git a/models/monsters/zombie.dpm_2.skin b/models/monsters/zombie.dpm_2.skin
new file mode 100644 (file)
index 0000000..e00379e
--- /dev/null
@@ -0,0 +1,2 @@
+bloodyskull,bloodyskull_robot
+meat,meat_robot
\ No newline at end of file
diff --git a/models/td/barricade.md3 b/models/td/barricade.md3
new file mode 100644 (file)
index 0000000..8d58d9b
Binary files /dev/null and b/models/td/barricade.md3 differ
diff --git a/monster_zombie.cfg b/monster_zombie.cfg
deleted file mode 100644 (file)
index 65d9e71..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-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
-
diff --git a/monsters.cfg b/monsters.cfg
new file mode 100644 (file)
index 0000000..78ca83f
--- /dev/null
@@ -0,0 +1,207 @@
+// 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_target_range 2000
+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)"
+set g_monsters_spawn_list "ogre demon shambler knight soldier scrag dog spawn hellknight fish vore enforcer zombie spider" "monsters not listed here will spawn as knights"
+
+// 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_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"
+set g_monster_shalrath_attack_spike_damage 30 "Vore homing spike explosion damage"
+set g_monster_shalrath_attack_spike_radius 60 "Vore homing spike explosion radius"
+set g_monster_shalrath_attack_spike_delay 2 "Delay between Vore homing spike attacks"
+set g_monster_shalrath_attack_melee_damage 30 "Vore magic attack damage"
+set g_monster_shalrath_attack_melee_delay 0.7 "Delay between Vore melee attacks"
+
+// 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
index fb4fdd5ef24e1fd245486a58d3a69ecf5fae33a8..a0fd4105f751538e06a09d6ef2c702b4975098b9 100644 (file)
@@ -289,6 +289,8 @@ void Ent_Projectile()
                        case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break;
                        case PROJECTILE_FLAC: setmodel(self, "models/hagarmissile.mdl"); self.scale = 0.4; self.traileffect = particleeffectnum("TR_SEEKER"); break;
                        case PROJECTILE_SEEKER: setmodel(self, "models/tagrocket.md3"); self.traileffect = particleeffectnum("TR_SEEKER"); break;
+                       
+                       case PROJECTILE_VORE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum(""); break;
 
                        case PROJECTILE_RAPTORBOMB:    setmodel(self, "models/vehicles/clusterbomb.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
                        case PROJECTILE_RAPTORBOMBLET: setmodel(self, "models/vehicles/bomblet.md3");     self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
index 2ec7db0623025d7bc813afae95a299956a0fd63c..a155c5f80fda19362a7e01ca6862bb31af7182ff 100644 (file)
@@ -1069,15 +1069,30 @@ vector HUD_DrawKeyValue(vector pos, string key, string value) {
 
 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)
@@ -1097,10 +1112,27 @@ vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
        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;
index c25fb059f6ebc2d52b017541eeb39e084ab4166b..6b362fed97614407ca395292065406b7dc128f41 100644 (file)
@@ -179,6 +179,12 @@ const float STAT_SECRETS_FOUND = 71;
 
 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;
@@ -354,6 +360,8 @@ float PROJECTILE_WAKICANNON     = 29;
 float PROJECTILE_BUMBLE_GUN     = 30;
 float PROJECTILE_BUMBLE_BEAM    = 31;
 
+float PROJECTILE_VORE_SPIKE            = 32;
+
 float SPECIES_HUMAN        =  0;
 float SPECIES_ROBOT_SOLID  =  1;
 float SPECIES_ALIEN        =  2;
index ca13f15a063e2fc674465312ebe1b600a1e88bd3..0735b6c6b73ba99f95501a54061c27d3cdeb01a0 100644 (file)
        DEATHTYPE(DEATH_KILL,                   DEATH_SELF_SUICIDE,                 NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_LAVA,                   DEATH_SELF_LAVA,                    DEATH_MURDER_LAVA,             NORMAL_POS) \
        DEATHTYPE(DEATH_MIRRORDAMAGE,           DEATH_SELF_BETRAYAL,                NO_MSG,                        NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_DOG_BITE,               DEATH_SELF_MON_DOG_BITE,                        NO_MSG,                                            DEATH_MONSTER_FIRST) \
+       DEATHTYPE(DEATH_MONSTER_DOG_JUMP,               DEATH_SELF_MON_DOG_JUMP,                        NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_ENFORCER,               DEATH_SELF_MON_ENFORCER,                        NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_FIEND,                  DEATH_SELF_MON_FIEND,                           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_FISH,                   DEATH_SELF_MON_FISH,                            NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_HKNIGHT_CRUSH,  DEATH_SELF_MON_HKNIGHT_CRUSH,           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_HKNIGHT_FBALL,  DEATH_SELF_MON_HKNIGHT_FBALL,           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_HKNIGHT_INFERNO,DEATH_SELF_MON_HKNIGHT_INFERNO,         NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_HKNIGHT_MELEE,  DEATH_SELF_MON_HKNIGHT_MELEE,           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_HKNIGHT_SPIKE,  DEATH_SELF_MON_HKNIGHT_SPIKE,           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_KNIGHT,                 DEATH_SELF_MON_KNIGHT,                          NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_OGRE_CHAINSAW,  DEATH_SELF_MON_OGRE_CHAINSAW,           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_OGRE_UZI,               DEATH_SELF_MON_OGRE_UZI,                        NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_MAGE,                   DEATH_SELF_MON_MAGE,                            NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SHAMBLER_CLAW,  DEATH_SELF_MON_SHAMBLER_CLAW,           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SHAMBLER_SMASH, DEATH_SELF_MON_SHAMBLER_SMASH,          NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SHAMBLER_ZAP,   DEATH_SELF_MON_SHAMBLER_ZAP,            NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_MARINE_SLAP,    DEATH_SELF_MON_MARINE_SLAP,                     NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_MARINE_UZI,             DEATH_SELF_MON_MARINE_UZI,                      NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SPIDER,                 DEATH_SELF_MON_SPIDER,                          NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_TARBABY,                DEATH_SELF_MON_TARBABY,                         NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SCRAG,                  DEATH_SELF_MON_SCRAG,                           NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_ZOMBIE_JUMP,    DEATH_SELF_MON_ZOMBIE_JUMP,                     NO_MSG,                                            NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_ZOMBIE_MELEE,   DEATH_SELF_MON_ZOMBIE_MELEE,            NO_MSG,                                            DEATH_MONSTER_LAST) \
        DEATHTYPE(DEATH_NOAMMO,                 DEATH_SELF_NOAMMO,                  NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_ROT,                    DEATH_SELF_ROT,                     NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_SHOOTING_STAR,          DEATH_SELF_SHOOTING_STAR,           DEATH_MURDER_SHOOTING_STAR,    NORMAL_POS) \
@@ -87,6 +111,7 @@ DEATHTYPES
 #define DEATH_ISSPECIAL(t)            ((t) >= DEATH_SPECIAL_START)
 #define DEATH_ISVEHICLE(t)            ((t) >= DEATH_VHFIRST && (t) <= DEATH_VHLAST)
 #define DEATH_ISTURRET(t)             ((t) >= DEATH_TURRET_FIRST && (t) <= DEATH_TURRET_LAST)
+#define DEATH_ISMONSTER(t)                       ((t) >= DEATH_MONSTER_FIRST && (t) <= DEATH_MONSTER_LAST)
 #define DEATH_WEAPONOFWEAPONDEATH(t)  ((t) & DEATH_WEAPONMASK)
 #define DEATH_ISWEAPON(t,w)           (!DEATH_ISSPECIAL(t) && DEATH_WEAPONOFWEAPONDEATH(t) == (w))
 #define DEATH_WEAPONOF(t)             (DEATH_ISSPECIAL(t) ? 0 : DEATH_WEAPONOFWEAPONDEATH(t))
index b4dbda25f2bc2b2617c37342f460e2e2715e6a8f..ad5fc99143529bc0ba1ce4516e053f1bc34605a0 100644 (file)
@@ -313,6 +313,8 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
                                        MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
                                else if(v == "team_CTF_blueflag")
                                        MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
+                               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")
index 10a35ee470a7e3cfbdfc70792dcf4b628977a3da..bc95ef28ecb24c225ab3ffa95ceb15b941ea7b69 100644 (file)
@@ -39,6 +39,12 @@ REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,"timelimit=20 pointlimit=30
 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(_("Real-Time Strategy"),rts,g_rts,RTS,"timelimit=0 pointlimit=10 leadlimit=0")
+#define g_rts IS_GAMETYPE(RTS)
+
 REGISTER_GAMETYPE(_("Arena"),arena,g_arena,ARENA,"timelimit=20 pointlimit=10 leadlimit=0")
 #define g_arena IS_GAMETYPE(ARENA)
 
index 04a1cec22250a1439a023bdf533cdaf85a86cd96..a32cc2b426c58e67f7b38776fbd70744d62a40f7 100644 (file)
@@ -226,6 +226,30 @@ void Send_Notification_WOVA(
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_FIRE,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 became a bit too crispy%s%s\n"), _("^BG%s^K1 felt a little hot%s%s\n")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_GENERIC,             2, 1, "s1 s2loc spree_lost", "s1",       "notify_selfkill",      _("^BG%s^K1 died%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_LAVA,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_lava",          _("^BG%s^K1 turned into hot slag%s%s\n"), _("^BG%s^K1 found a hot place%s%s\n")) \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_DOG_BITE,            2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was mauled by a Rottweiler%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_DOG_JUMP,            2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 didn't see the pouncing Rottweiler%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ENFORCER,            2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was blasted by an Enforcer%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_FIEND,           2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was eviscerated by a Fiend%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_FISH,                2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was fed to the Rotfish%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_HKNIGHT_CRUSH,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was crushed by a pouncing Hell-Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_HKNIGHT_FBALL,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was turned to ash by a Hell-Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_HKNIGHT_INFERNO, 2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was burned to death by a Hell-Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_HKNIGHT_MELEE,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was slain by a Hell-Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_HKNIGHT_SPIKE,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was cursed by a Hell-Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_KNIGHT,              2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was sliced up by a Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_OGRE_CHAINSAW,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was cut down by an Ogre%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_OGRE_UZI,            2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was nailed by an Ogre%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MAGE,                2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was exploded by a Mage%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_CLAW,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1's innards became outwards by a Shambler%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_SMASH,  2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was smashed by a Shambler%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_ZAP,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was zapped to death by a Shambler%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MARINE_SLAP,     2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was slapped to death by a Marine%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MARINE_UZI,      2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was gunned down by a Marine%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SPIDER,              2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was bitten by a Spider%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_TARBABY,         2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 came too close to an exploding Pumpkin%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SCRAG,           2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was cursed by a Scrag%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_JUMP,     2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 joins the Zombies%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_MELEE,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was given kung fu lessons by a Zombie%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NOAMMO,              2, 1, "s1 s2loc spree_lost", "s1",       "notify_outofammo",     _("^BG%s^K1 died%s%s. What's the point of living without ammo?\n"), _("^BG%s^K1 ran out of ammo%s%s\n")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_ROT,                 2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 rotted away%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_SHOOTING_STAR,       2, 1, "s1 s2loc spree_lost", "s1",       "notify_shootingstar",  _("^BG%s^K1 became a shooting star%s%s\n"), "") \
@@ -279,6 +303,7 @@ void Send_Notification_WOVA(
        MULTITEAM_INFO(1, INFO_KEYHUNT_PICKUP_, 4,             1, 0, "s1", "",                          "",                     _("^BG%s^BG picked up the ^TC^TT Key\n"), "") \
        MSG_INFO_NOTIF(1, INFO_LMS_FORFEIT,                    1, 0, "s1", "",                          "",                     _("^BG%s^F3 forfeited\n"), "") \
        MSG_INFO_NOTIF(1, INFO_LMS_NOLIVES,                    1, 0, "s1", "",                          "",                     _("^BG%s^F3 has no more lives left\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_MONSTERS_DISABLED,                  0, 0, "", "",                            "",                     _("^BGMonsters are currently disabled\n"), "") \
        MSG_INFO_NOTIF(1, INFO_POWERUP_INVISIBILITY,           1, 0, "s1", "s1",                        "strength",             _("^BG%s^K1 picked up Invisibility\n"), "") \
        MSG_INFO_NOTIF(1, INFO_POWERUP_SHIELD,                 1, 0, "s1", "s1",                        "shield",               _("^BG%s^K1 picked up Shield\n"), "") \
        MSG_INFO_NOTIF(1, INFO_POWERUP_SPEED,                  1, 0, "s1", "s1",                        "shield",               _("^BG%s^K1 picked up Speed\n"), "") \
@@ -298,6 +323,26 @@ void Send_Notification_WOVA(
        MULTITEAM_INFO(1, INFO_SCORES_, 4,                     0, 0, "", "",                            "",                     _("^TC^TT ^BGteam scores!\n"), "") \
        MSG_INFO_NOTIF(1, INFO_SPECTATE_WARNING,               0, 1, "f1secs", "",                      "",                     _("^F2You have to become a player within the next %s, otherwise you will be kicked, because spectating isn't allowed at this time!\n"), "") \
        MSG_INFO_NOTIF(1, INFO_SUPERWEAPON_PICKUP,             1, 0, "s1", "s1",                        "strength",             _("^BG%s^K1 picked up a Superweapon\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_AIM_REMOVE,                      0, 0, "", "",                            "",                     _("^BGYou need to aim at your turret to remove it\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_AIM_REPAIR,                      0, 0, "", "",                            "",                     _("^BGYou need to aim at your turret to repair it\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_AIM_UPGRADE,                     0, 0, "", "",                            "",                     _("^BGYou need to aim at your turret to upgrade it\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_CANTSPAWN,                       0, 0, "", "",                            "",                     _("^BGYou can't currently spawn a turret\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_GENDESTROYED,                    0, 0, "", "",                            "",                     _("^K1A generator was destroyed!\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_INVALID,                         0, 0, "", "",                            "",                     _("^K1Invalid turret. Check '^F2turretspawn list^K1' to see available turrets\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_LIST,                                    1, 0, "s1", "",                          "",                     _("^BGAvailable turrets: ^F2%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_MAXHEALTH,                       0, 0, "", "",                            "",                     _("^K1This turret is already at max health\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_MAXPOWER,                        0, 0, "", "",                            "",                         _("^K1This turret is already at max power\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_MAXTURRETS,                      0, 1, "f1", "",                          "",                         _("^K1You can't spawn more than %s turrets\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_NOFUEL,                          0, 0, "", "",                            "",                     _("^K1You don't have enough fuel to spawn that turret\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_NOFUEL_REPAIR,                   0, 1, "f1", "",                          "",                         _("^K1You need %s fuel to repair this turret\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_NOFUEL_UPGRADE,              0, 1, "f1", "",                          "",                     _("^K1You need %s fuel to increase this turret's power\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_PHASE_BUILD,                     0, 3, "f1 f2 f3", "",                    "",                     _("^BGWave %s^BG build phase... Next monsters: ^F2%s^BG, wave starts in ^F2%s seconds\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_PHASE_COMBAT,                    0, 0, "", "",                            "",                     _("^K1Combat phase!\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_REMOVE,                          0, 0, "", "",                            "",                     _("^BGTurret removed\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_REPAIR,                          0, 0, "", "",                            "",                     _("^F1Turret repaired by 100 health points!\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_SPAWN,                                   0, 0, "", "",                            "",                     _("^BGYou spawned a turret\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_UPGRADE,                         0, 0, "", "",                            "",                     _("^F1Turret power increased by 20 percent!\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_VICTORY,                         1, 0, "s1", "",                          "",                     _("^F1%s^F1 victory!\n"), "") \
        MSG_INFO_NOTIF(2, INFO_VERSION_BETA,                   2, 0, "s1 s2", "",                       "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s\n"), "") \
        MSG_INFO_NOTIF(2, INFO_VERSION_OLD,                    2, 0, "s1 s2", "",                       "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s^BG, you have ^F2Xonotic %s\n"), "") \
        MSG_INFO_NOTIF(2, INFO_VERSION_OUTDATED,               2, 0, "s1 s2", "",                       "",                     _("^F4NOTE: ^F1Xonotic %s^BG is out, and you still have ^F2Xonotic %s^BG - get the update from ^F3http://www.xonotic.org/^BG!\n"), "") \
@@ -404,6 +449,7 @@ void Send_Notification_WOVA(
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_FIRE,             0, 0, "",             NO_CPID,             "0 0", _("^K1You got a little bit too crispy!"), _("^K1You felt a little too hot!")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_GENERIC,          0, 0, "",             NO_CPID,             "0 0", _("^K1You killed your own dumb self!"), _("^K1You need to be more careful!")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_LAVA,             0, 0, "",             NO_CPID,             "0 0", _("^K1You couldn't stand the heat!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_MONSTER,          0, 0, "",             NO_CPID,             "0 0", _("^K1You were killed by a monster!"), _("^K1You need to watch out for monsters!")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NOAMMO,           0, 0, "",             NO_CPID,             "0 0", _("^K1You were killed for running out of ammo..."), _("^K1You are respawning for running out of ammo...")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_ROT,              0, 0, "",             NO_CPID,             "0 0", _("^K1You grew too old without taking your medicine"), _("^K1You need to preserve your health")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_SHOOTING_STAR,    0, 0, "",             NO_CPID,             "0 0", _("^K1You became a shooting star!"), "") \
@@ -471,12 +517,19 @@ void Send_Notification_WOVA(
        MSG_CENTER_NOTIF(1, CENTER_SUPERWEAPON_BROKEN,          0, 0, "",              CPID_POWERUP,          "0 0", _("^F2Superweapons have broken down"), "") \
        MSG_CENTER_NOTIF(1, CENTER_SUPERWEAPON_LOST,            0, 0, "",              CPID_POWERUP,          "0 0", _("^F2Superweapons have been lost"), "") \
        MSG_CENTER_NOTIF(1, CENTER_SUPERWEAPON_PICKUP,          0, 0, "",              CPID_POWERUP,          "0 0", _("^F2You now have a superweapon"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_ANNOUNCE_SPAWN,           1, 0, "s1",            CPID_TOWERDEFENSE,     "3 0", _("^K1A ^K2%s^K1 has arrived!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_GENDAMAGED,                       0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^K1The generator is under attack!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_GENDESTROYED,             0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^K1A generator was destroyed!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_PHASE_BUILD,              0, 3, "f1 f2 f3",      CPID_TOWERDEFENSE,     "5 0", _("^BGWave ^F2%s^BG build phase... Next monsters: ^F2%s^BG, wave starts in ^F2%s seconds"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_PHASE_COMBAT,             0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^K1Combat phase!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_VICTORY,                          1, 0, "s1",            CPID_TOWERDEFENSE,     "0 0", _("^F1%s^F1 victory!"), "") \
        MULTITEAM_CENTER(1, CENTER_TEAMCHANGE_, 4,              0, 1, "",              CPID_TEAMCHANGE,       "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "") \
        MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_AUTO,             0, 1, "",              CPID_TEAMCHANGE,       "1 f1", _("^K1Changing team in ^COUNT"), "") \
        MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_SPECTATE,         0, 1, "",              CPID_TEAMCHANGE,       "1 f1", _("^K1Spectating in ^COUNT"), "") \
        MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_SUICIDE,          0, 1, "",              CPID_TEAMCHANGE,       "1 f1", _("^K1Suicide in ^COUNT"), "") \
        MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_BEGINNING,           0, 1, "",              CPID_TIMEOUT,          "1 f1", _("^F4Timeout begins in ^COUNT"), "") \
-       MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING,              0, 1, "",              CPID_TIMEOUT,          "1 f1", _("^F4Timeout ends in ^COUNT"), "")
+       MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING,              0, 1, "",              CPID_TIMEOUT,          "1 f1", _("^F4Timeout ends in ^COUNT"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_ZA_WIN,                              0, 0, "",              NO_CPID,                   "0 0",  _("^F1All zombies exterminated, prepare for the next round!"), "")
 
 #define MSG_MULTI_NOTIFICATIONS \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_CHEAT,                   INFO_DEATH_MURDER_CHEAT,                   NO_MSG) \
@@ -512,6 +565,30 @@ void Send_Notification_WOVA(
        MSG_MULTI_NOTIF(1, DEATH_SELF_FIRE,                      INFO_DEATH_SELF_FIRE,                      CENTER_DEATH_SELF_FIRE) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_GENERIC,                   INFO_DEATH_SELF_GENERIC,                   CENTER_DEATH_SELF_GENERIC) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_LAVA,                      INFO_DEATH_SELF_LAVA,                      CENTER_DEATH_SELF_LAVA) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_DOG_BITE,                              INFO_DEATH_SELF_MON_DOG_BITE,                          CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_DOG_JUMP,                              INFO_DEATH_SELF_MON_DOG_JUMP,                          CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ENFORCER,                              INFO_DEATH_SELF_MON_ENFORCER,                          CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_FIEND,                                 INFO_DEATH_SELF_MON_FIEND,                                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_FISH,                                  INFO_DEATH_SELF_MON_FISH,                                      CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_HKNIGHT_CRUSH,                 INFO_DEATH_SELF_MON_HKNIGHT_CRUSH,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_HKNIGHT_FBALL,                 INFO_DEATH_SELF_MON_HKNIGHT_FBALL,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_HKNIGHT_INFERNO,               INFO_DEATH_SELF_MON_HKNIGHT_INFERNO,           CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_HKNIGHT_MELEE,                 INFO_DEATH_SELF_MON_HKNIGHT_MELEE,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_HKNIGHT_SPIKE,                 INFO_DEATH_SELF_MON_HKNIGHT_SPIKE,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_KNIGHT,                                INFO_DEATH_SELF_MON_KNIGHT,                            CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_OGRE_CHAINSAW,                 INFO_DEATH_SELF_MON_OGRE_CHAINSAW,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_OGRE_UZI,                              INFO_DEATH_SELF_MON_OGRE_UZI,                          CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_MAGE,                                  INFO_DEATH_SELF_MON_MAGE,                                      CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_CLAW,                 INFO_DEATH_SELF_MON_SHAMBLER_CLAW,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_SMASH,                INFO_DEATH_SELF_MON_SHAMBLER_SMASH,            CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_ZAP,                  INFO_DEATH_SELF_MON_SHAMBLER_ZAP,                      CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_MARINE_SLAP,                   INFO_DEATH_SELF_MON_MARINE_SLAP,                       CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_MARINE_UZI,                    INFO_DEATH_SELF_MON_MARINE_UZI,                        CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SPIDER,                                INFO_DEATH_SELF_MON_SPIDER,                            CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_TARBABY,                               INFO_DEATH_SELF_MON_TARBABY,                           CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SCRAG,                                 INFO_DEATH_SELF_MON_SCRAG,                                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_JUMP,                   INFO_DEATH_SELF_MON_ZOMBIE_JUMP,                       CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_MELEE,                  INFO_DEATH_SELF_MON_ZOMBIE_MELEE,                      CENTER_DEATH_SELF_MONSTER) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_NOAMMO,                    INFO_DEATH_SELF_NOAMMO,                    CENTER_DEATH_SELF_NOAMMO) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_ROT,                       INFO_DEATH_SELF_ROT,                       CENTER_DEATH_SELF_ROT) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_SHOOTING_STAR,             INFO_DEATH_SELF_SHOOTING_STAR,             CENTER_DEATH_SELF_SHOOTING_STAR) \
@@ -548,6 +625,10 @@ void Send_Notification_WOVA(
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_NOAMMO,                   INFO_ITEM_WEAPON_NOAMMO,                   CENTER_ITEM_WEAPON_NOAMMO) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_PRIMORSEC,                INFO_ITEM_WEAPON_PRIMORSEC,                CENTER_ITEM_WEAPON_PRIMORSEC) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_UNAVAILABLE,              INFO_ITEM_WEAPON_UNAVAILABLE,              CENTER_ITEM_WEAPON_UNAVAILABLE) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_GENDESTROYED,                INFO_TD_GENDESTROYED,                                  CENTER_TD_GENDESTROYED) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_BUILD,                 INFO_TD_PHASE_BUILD,                                   CENTER_TD_PHASE_BUILD) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_COMBAT,                INFO_TD_PHASE_COMBAT,                                  CENTER_TD_PHASE_COMBAT) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_VICTORY,                             INFO_TD_VICTORY,                                       CENTER_TD_VICTORY) \
        MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_MURDER,              INFO_WEAPON_ACCORDEON_MURDER,              NO_MSG) \
        MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_SUICIDE,             INFO_WEAPON_ACCORDEON_SUICIDE,             CENTER_DEATH_SELF_GENERIC) \
        MSG_MULTI_NOTIF(1, WEAPON_CRYLINK_MURDER,                INFO_WEAPON_CRYLINK_MURDER,                NO_MSG) \
index 0a3a55c5f1d4bc214045b2c32731adac5779c156..4a50f1db886d83af6eed627993dc4c9d580e1f9b 100644 (file)
@@ -29,6 +29,7 @@
 #include "xonotic/dialog_firstrun.c"
 #include "xonotic/dialog_teamselect.c"
 #include "xonotic/dialog_sandboxtools.c"
+#include "xonotic/dialog_monstertools.c"
 #include "xonotic/dialog_settings.c"
 #include "xonotic/dialog_settings_video.c"
 #include "xonotic/dialog_settings_effects.c"
@@ -37,6 +38,7 @@
 #include "xonotic/dialog_settings_misc.c"
 #include "xonotic/dialog_multiplayer.c"
 #include "xonotic/dialog_multiplayer_playersetup.c"
+#include "xonotic/dialog_towerdefense.c"
 #include "xonotic/tabcontroller.c"
 #include "xonotic/textlabel.c"
 #include "xonotic/slider.c"
diff --git a/qcsrc/menu/xonotic/dialog_monstertools.c b/qcsrc/menu/xonotic/dialog_monstertools.c
new file mode 100644 (file)
index 0000000..2aacd15
--- /dev/null
@@ -0,0 +1,70 @@
+#ifdef INTERFACE
+CLASS(XonoticMonsterToolsDialog) EXTENDS(XonoticRootDialog)
+       METHOD(XonoticMonsterToolsDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+       ATTRIB(XonoticMonsterToolsDialog, title, string, _("Monster Tools"))
+       ATTRIB(XonoticMonsterToolsDialog, color, vector, SKINCOLOR_DIALOG_SANDBOXTOOLS)
+       ATTRIB(XonoticMonsterToolsDialog, intendedWidth, float, 0.8)
+       ATTRIB(XonoticMonsterToolsDialog, rows, float, 16)
+       ATTRIB(XonoticMonsterToolsDialog, columns, float, 4)
+       ATTRIB(XonoticMonsterToolsDialog, name, string, "MonsterTools")
+ENDCLASS(XonoticMonsterToolsDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticMonsterToolsDialog_fill(entity me)
+{
+       entity e, box;
+
+       me.TR(me);
+               me.TD(me, 1, 0.25, e = makeXonoticTextLabel(0, _("Monster:")));
+       me.TR(me);
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "zombie", _("Zombie")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "ogre", _("Ogre")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "demon", _("Fiend")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "spider", _("Spider")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "knight", _("Knight")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "hellknight", _("Hell-Knight")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "shambler", _("Shambler")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "soldier", _("Marine")));
+       me.TR(me);
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "dog", _("Cerberus")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "spawn", _("Tarbaby")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "fish", _("Rotfish")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "vore", _("Mage")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "enforcer", _("Enforcer")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "scrag", _("Scrag")));
+               me.TD(me, 1, 0.5, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "pony", _("Twilight Sparkle")));
+       me.TR(me);
+               me.TD(me, 1, 0.25, e = makeXonoticTextLabel(0, _("Name:")));
+               me.TD(me, 1, 1.5, box = makeXonoticInputBox(1, "menu_monsters_edit_name"));
+                       box.forbiddenCharacters = "\r\n\\\"$"; // don't care, isn't getting saved
+                       box.maxLength = -127; // negative means encoded length in bytes
+                       box.saveImmediately = 1;
+               me.TDempty(me, 0.1);
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Spawn"), '0 0 0', "cmd mobspawn $menu_monsters_edit_spawn $menu_monsters_edit_movetarget \"$menu_monsters_edit_name\"", 0));
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Remove"), '0 0 0', "cmd mobkill", 0));
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Change name"), '0 0 0', "editmob name \"$menu_monsters_edit_name\"", 0));
+       me.TR(me);
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Move target:"), '0 0 0', "editmob movetarget $menu_monsters_edit_movetarget", 0));
+               me.TD(me, 1, 0.5, e = makeXonoticRadioButton(2, "menu_monsters_edit_movetarget", "1", _("Follow")));
+               me.TD(me, 1, 0.5, e = makeXonoticRadioButton(2, "menu_monsters_edit_movetarget", "2", _("Wander")));
+               me.TD(me, 1, 0.5, e = makeXonoticRadioButton(2, "menu_monsters_edit_movetarget", "3", _("Spawnpoint")));
+               me.TD(me, 1, 0.5, e = makeXonoticRadioButton(2, "menu_monsters_edit_movetarget", "4", _("No moving")));
+       me.TR(me);
+       me.TD(me, 1, 1.5, e = makeXonoticTextLabel(0, _("Colors:")));
+       me.TR(me);
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Set skin:"), '0 0 0', "editmob skin $menu_monsters_edit_skin", 0));
+               me.TD(me, 1, 1.5, e = makeXonoticSlider(0, 99, 1, "menu_monsters_edit_skin"));
+       me.TR(me);
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Set color:"), '0 0 0', "editmob color \"$menu_monsters_edit_color\"", 0));
+               me.TD(me, 2, 1.5, e = makeXonoticColorpickerString("menu_monsters_edit_color", "menu_monsters_edit_color"));
+       me.TR(me);
+
+       me.gotoRC(me, me.rows - 1, 0);
+               me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
+                       e.onClick = Dialog_Close;
+                       e.onClickEntity = me;
+}
+#endif
+
+/* Click. The c-word is here so you can grep for it :-) */
diff --git a/qcsrc/menu/xonotic/dialog_towerdefense.c b/qcsrc/menu/xonotic/dialog_towerdefense.c
new file mode 100644 (file)
index 0000000..4252ecf
--- /dev/null
@@ -0,0 +1,42 @@
+#ifdef INTERFACE
+CLASS(XonoticTowerDefenseDialog) EXTENDS(XonoticRootDialog)
+       METHOD(XonoticTowerDefenseDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+       ATTRIB(XonoticTowerDefenseDialog, title, string, _("Tower Defense Control Panel"))
+       ATTRIB(XonoticTowerDefenseDialog, color, vector, SKINCOLOR_DIALOG_SANDBOXTOOLS)
+       ATTRIB(XonoticTowerDefenseDialog, intendedWidth, float, 0.8)
+       ATTRIB(XonoticTowerDefenseDialog, rows, float, 4)
+       ATTRIB(XonoticTowerDefenseDialog, columns, float, 2)
+       ATTRIB(XonoticTowerDefenseDialog, name, string, "TowerDefense")
+ENDCLASS(XonoticTowerDefenseDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticTowerDefenseDialog_fill(entity me)
+{
+       entity e;
+
+       me.TR(me);
+               me.TD(me, 1, 0.5, e = makeXonoticTextLabel(0, _("Turret:")));
+       me.TR(me);
+               me.TD(me, 1, 0.2, e = makeXonoticRadioButton(2, "menu_td_edit_spawn", "plasma", _("Plasma")));
+               me.TD(me, 1, 0.2, e = makeXonoticRadioButton(2, "menu_td_edit_spawn", "mlrs", _("MLRS")));
+               me.TD(me, 1, 0.2, e = makeXonoticRadioButton(2, "menu_td_edit_spawn", "flac", _("FLAC")));
+               me.TD(me, 1, 0.2, e = makeXonoticRadioButton(2, "menu_td_edit_spawn", "barricade", _("Barricade")));
+               me.TD(me, 1, 0.2, e = makeXonoticRadioButton(2, "menu_td_edit_spawn", "walker", _("Walker")));
+               me.TR(me);
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Spawn"), '0 0 0', "spawnturret $menu_td_edit_spawn", 0));
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Remove"), '0 0 0', "rmturret", 0));
+               me.TDempty(me, 0.1);
+               me.TD(me, 1, 0.2, e = makeXonoticCommandButton(_("Repair"), '0 0 0', "repairturret", 0));
+               me.TD(me, 1, 0.2, e = makeXonoticCommandButton(_("Upgrade"), '0 0 0', "upgradeturret", 0));
+               
+       me.TR(me);
+
+       me.gotoRC(me, me.rows - 1, 0);
+               me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
+                       e.onClick = Dialog_Close;
+                       e.onClickEntity = me;
+}
+#endif
+
+/* Click. The c-word is here so you can grep for it :-) */
index f9d86c2dd229276aadb7272c9484f165bb1ad606..32c1034c830e87cf1c34e84909552f192fe41fff 100644 (file)
@@ -193,12 +193,20 @@ void MainWindow_configureMainWindow(entity me)
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z * SKINALPHA_DIALOG_SANDBOXTOOLS);
        
+       i = spawnXonoticTowerDefenseDialog();
+       i.configureDialog(i);
+       me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z * SKINALPHA_DIALOG_SANDBOXTOOLS);
+       
        
        // miscellaneous dialogs
        i = spawnXonoticTeamSelectDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
        
+       i = spawnXonoticMonsterToolsDialog();
+       i.configureDialog(i);
+       me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z * SKINALPHA_DIALOG_SANDBOXTOOLS);
+       
        
        // main dialogs/windows
        me.mainNexposee = n = spawnXonoticNexposee();
index 9271e03d0ad20ef4b7e8e9a6536d68b723b40348..c5f3bc740f0123f4bb68986182c86b41cd02a269 100644 (file)
@@ -110,7 +110,8 @@ void accuracy_add(entity e, float w, float fired, float hit)
 float accuracy_isgooddamage(entity attacker, entity targ)
 {
        if(!inWarmupStage)
-       if(targ.flags & FL_CLIENT)
+       if((g_td && targ.flags & FL_MONSTER) || (!g_td && targ.flags & FL_CLIENT))
+       if(!(attacker.flags & FL_MONSTER)) // no accuracy for monsters
        if(targ.deadflag == DEAD_NO)
        if(IsDifferentTeam(attacker, targ))
                return TRUE;
index 1febc23d26801842d3f916d02357612009789d63..7c9177436b433bf19206973a1ce0a3bdc3e2cc1a 100644 (file)
@@ -1222,3 +1222,51 @@ float autocvar_physics_ode;
 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_monsters_skill_start;
+float autocvar_g_td_monsters_skill_increment;
+float autocvar_g_td_monsters_speed_walk;
+float autocvar_g_td_monsters_speed_run;
+float autocvar_g_td_monsters_spawn_delay;
+float autocvar_g_td_monsters_spawnshield_time;
+float autocvar_g_td_monsters_ignore_turrets;
+float autocvar_g_td_turret_upgrade_cost;
+float autocvar_g_td_turret_repair_cost;
+float autocvar_g_td_barricade_damage;
+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_target_range;
+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;
+string autocvar_g_monsters_spawn_list;
index 4c7d5c57564d8f7b674c610629e45faac08ab7b7..bcff1babf0db1e8939db14cb2a390329ce5d74be 100644 (file)
@@ -851,6 +851,7 @@ void PutClientInServer (void)
                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;
@@ -905,6 +906,8 @@ void PutClientInServer (void)
                                self.target = s;
                        activator = world;
                self = oldself;
+               
+               Unfreeze(self);
 
                spawn_spot = spot;
                MUTATOR_CALLHOOK(PlayerSpawn);
@@ -2590,6 +2593,15 @@ void PlayerPreThink (void)
                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
@@ -2756,7 +2768,7 @@ void PlayerPreThink (void)
                        do_crouch = 1;
                if(self.vehicle)
                        do_crouch = 0;
-               if(self.freezetag_frozen)
+               if(self.freezetag_frozen || self.frozen)
                        do_crouch = 0;
                if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
                        do_crouch = 0;
@@ -2828,6 +2840,9 @@ void PlayerPreThink (void)
                // 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
index 6ddf7b69ed806e75bf81c1cea5de0307176a03b6..9affe67e00c1a17e4d13bcd4afeb61e9fdfbdf67 100644 (file)
@@ -23,7 +23,7 @@ When you press the jump key
 */
 void PlayerJump (void)
 {
-       if(self.freezetag_frozen)
+       if(self.freezetag_frozen || self.frozen)
                return; // no jumping in freezetag when frozen
 
        float mjumpheight;
@@ -855,6 +855,12 @@ void SV_PlayerPhysics()
        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);
 
@@ -1057,7 +1063,7 @@ void SV_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);
index 944db7e5b8b07dd7588c90679c5ad0ba1b872a12..a92d15b2b42e067be4d1545c4b2dee9da94172ee 100644 (file)
@@ -245,7 +245,7 @@ void player_anim (void)
                else
                        deadbits = ANIMSTATE_DEAD2;
        float animbits = deadbits;
-       if(self.freezetag_frozen)
+       if(self.freezetag_frozen || self.frozen)
                animbits |= ANIMSTATE_FROZEN;
        if(self.crouch)
                animbits |= ANIMSTATE_DUCK;
index 2a1297dc2be398d5a204852f84c880e862c54820..498ced32ff91411369904a4ee9d7c5b481ae931f 100644 (file)
@@ -334,6 +334,8 @@ void W_ThrowWeapon(vector velo, vector delta, float doreduce)
        w = self.weapon;
        if (w == 0)
                return; // just in case
+       if(self.frozen)
+               return;
        if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon))
                return;
        if(!autocvar_g_weapon_throwable)
@@ -365,7 +367,7 @@ void W_WeaponFrame()
        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)
index afabe1820284522acf4d4dc0211d610d277de559..162f9cf8655626f8500d5e3228dc4ee275fba3db 100644 (file)
@@ -88,7 +88,7 @@ void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright,
        vector org;
        float lag;
 
-       if(player.hitplotfh >= 0)
+       if(player.hitplotfh >= 0 && !(player.flags & FL_MONSTER))
        {
                lag = ANTILAG_LATENCY(player);
                if(lag < 0.001)
@@ -1010,6 +1010,8 @@ vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float f
 
 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;
@@ -1273,6 +1275,9 @@ void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)
 
 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;
 
index 6c1e0ed56f58e79094ed4fcb1ce461bab8533162..fb447f056358a942092aa2605a35216364e0549b 100644 (file)
@@ -184,6 +184,132 @@ void ClientCommand_join(float request)
        }
 }
 
+void ClientCommand_mobedit(float request, float argc)
+{
+       switch(request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       makevectors(self.v_angle);
+                       WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+                       
+                       if not(trace_ent.flags & FL_MONSTER) { sprint(self, "You need to aim at your monster to edit its properties.\n"); return; }
+                       if(trace_ent.realowner != self) { sprint(self, "That monster does not belong to you.\n"); return; }
+                       
+                       switch(argv(1))
+                       {
+                               case "name": trace_ent.netname = strzone(strdecolorize(argv(2))); WaypointSprite_UpdateSprites(trace_ent.sprite, trace_ent.netname, "", ""); break;
+                               case "skin": trace_ent.skin = stof(argv(2)); break;
+                               case "color": trace_ent.colormod = stov(argv(2)); break;
+                               case "movetarget": trace_ent.monster_moveflags = stof(argv(2)); break;
+                               default: sprint(self, "Unknown parameter\n"); break;
+                       }
+                       
+                       return; // never fall through to usage
+               }
+               default:
+                       sprint(self, "Incorrect parameters for ^2mobedit^7\n");
+               case CMD_REQUEST_USAGE:
+               {
+                       sprint(self, "\nUsage:^3 cmd mobedit [argument]\n");
+                       sprint(self, "  Where 'argument' can be name, skin, color or movetarget.\n");
+                       return;
+               }
+       }
+}
+       
+void ClientCommand_mobkill(float request)
+{
+       switch(request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       makevectors(self.v_angle);
+                       WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+                       
+                       if(trace_ent.flags & FL_MONSTER)
+                       {
+                               if(trace_ent.realowner != self)
+                               {
+                                       sprint(self, "That monster does not belong to you.\n");
+                                       return;
+                               }
+                               sprint(self, strcat("Your pet '", trace_ent.netname, "' has been brutally mutilated.\n"));
+                               Damage (trace_ent, world, world, trace_ent.health + trace_ent.max_health, DEATH_KILL, trace_ent.origin, '0 0 0');
+                               return;
+                       }
+                       else
+                               sprint(self, "You need to aim at your monster to kill it.\n");
+                       
+                       return;
+               }
+       
+               default:
+                       sprint(self, "Incorrect parameters for ^2mobkill^7\n");
+               case CMD_REQUEST_USAGE:
+               {
+                       sprint(self, "\nUsage:^3 cmd mobkill\n");
+                       sprint(self, "  Aim at your monster to kill it.\n");
+                       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(autocvar_g_monsters_spawn_list, "\n"));
+                               return;
+                       }
+                       
+                       if(self.classname != STR_PLAYER) { sprint(self, "You can't spawn monsters while spectating.\n"); }
+                       else if not(autocvar_g_monsters) { sprint(self, "Monsters aren't enabled.\n"); }
+                       else if(g_td) { sprint(self, "You can't spawn monsters in Tower Defense mode.\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 * 150, 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)
@@ -564,6 +690,9 @@ void ClientCommand_(float 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("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \
+       CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \
+       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") \
index e64cbc267ac505e61d579318cd644a2e7b2d4595..b0118c5cf327503b77ee3245f3b9082cd4a37b72 100644 (file)
@@ -8,7 +8,12 @@
 .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);
index 0944f04fbbe06799f4f523d37eb243af42bddb92..2a967151d199ddeb516731976b708bb60b1cd988 100644 (file)
@@ -139,6 +139,51 @@ void GameCommand_adminmsg(float request, float argc)
        }
 }
 
+void GameCommand_butcher(float request)
+{
+       switch(request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       if(g_td) { print("This command doesn't work in Tower Defense.\n"); return; }
+               
+            float removed_count = 0;
+                       entity montokill, head;
+                       
+            FOR_EACH_MONSTER(montokill)
+            {
+                               WaypointSprite_Kill(montokill.sprite);
+                               
+                               if(montokill.weaponentity)
+                                       remove(montokill.weaponentity);
+                    
+                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)
@@ -1724,6 +1769,7 @@ void GameCommand_(float 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") \
index 668433ce0058ad55f603b9062a23bf4e3e478d5a..39a8af1fd61eb309fa56359fa69e17c756982410 100644 (file)
@@ -593,6 +593,10 @@ float serverflags;
 .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.
 
@@ -623,3 +627,19 @@ string modname;
 #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
+.float monster_moveflags; // checks where to move when not attacking
index 29eed9af8cf5aecd2d3ebbe65597a86eb711e8ab..a667590666539eebb447b26984582f264744be11 100644 (file)
@@ -566,6 +566,67 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype)
        if(targ.killcount) { targ.killcount = 0; }
 }
 
+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;
@@ -754,6 +815,12 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        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;
@@ -803,7 +870,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        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))
                                {
@@ -1288,7 +1355,7 @@ void Fire_ApplyDamage(entity e)
                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);
index f7af47d924036f121a6b8340a8dc2df5a0edaee4..ad1da10f7f4be77db9181e8f87cb89cbc963f640 100644 (file)
@@ -302,7 +302,7 @@ void FireGrapplingHook (void)
        if((arena_roundbased && time < warmup) || (time < game_starttime))
                return;
 
-  if(self.freezetag_frozen)
+  if(self.freezetag_frozen || self.frozen)
                return;
        
        if(self.vehicle)
index a9b958568948339d897aff0fc110fe0ccfe33811..0d6e29360328cbff338e2acefc03cd2e9e6b843d 100644 (file)
@@ -264,6 +264,7 @@ void cvar_changes_init()
                BADCVAR("g_freezetag");
                BADCVAR("g_keepaway");
                BADCVAR("g_keyhunt");
+               BADCVAR("g_td");
                BADCVAR("g_keyhunt_teams");
                BADCVAR("g_keyhunt_teams");
                BADCVAR("g_lms");
@@ -817,6 +818,16 @@ void spawnfunc_worldspawn (void)
                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);
@@ -827,6 +838,10 @@ void spawnfunc_worldspawn (void)
        // 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);
@@ -939,6 +954,8 @@ void spawnfunc_worldspawn (void)
        // 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);
 
@@ -2104,6 +2121,36 @@ float WinningCondition_RanOutOfSpawns()
                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
@@ -2259,6 +2306,10 @@ void 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);
index 17a26821d9a997005184152cf57c5304dcd2eaf4..494da71ffe30bf03912873c61cb7b548d877478e 100644 (file)
@@ -99,6 +99,8 @@ string STR_OBSERVER = "observer";
 #define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if not(IS_PLAYER(v)) // Samual: shouldn't this be IS_SPEC(v)? and rather create a separate macro to include observers too
 #define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(IS_PLAYER(v))
 
+#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
+
 #define CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
 
 // copies a string to a tempstring (so one can strunzone it)
@@ -1006,6 +1008,8 @@ string GetGametype(); // g_world.qc
 void readlevelcvars(void)
 {
        g_minstagib = cvar("g_minstagib");
+    
+        monster_skill = cvar("g_monsters_skill");
 
        // load ALL the mutators
        if(cvar("g_dodging"))
@@ -1033,6 +1037,9 @@ void readlevelcvars(void)
        // 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;
diff --git a/qcsrc/server/monsters/lib/defs.qh b/qcsrc/server/monsters/lib/defs.qh
new file mode 100644 (file)
index 0000000..422885a
--- /dev/null
@@ -0,0 +1,48 @@
+.float sprite_height;
+
+.void()                attack_melee;
+.float()       attack_ranged;
+.float()       checkattack;
+
+.float candrop;
+
+.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
+
+.float msound_delay; // restricts some monster sounds
+.string msound_idle;
+.string msound_death;
+.string msound_attack_melee;
+.string msound_attack_ranged;
+.string msound_spawn;
+.string msound_sight;
+.string msound_pain;
+
+.void() monster_spawnfunc;
+.void() monster_die;
+.void() monster_delayedattack;
+
+.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 MONSTER_STATE_ATTACK_LEAP = 1; // the start of something big?
diff --git a/qcsrc/server/monsters/lib/monsters.qc b/qcsrc/server/monsters/lib/monsters.qc
new file mode 100644 (file)
index 0000000..3199287
--- /dev/null
@@ -0,0 +1,810 @@
+// TODO: clean up this file?
+
+void M_Item_Touch ()
+{
+       if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO)
+       {
+               Item_Touch();
+               self.think = SUB_Remove;
+               self.nextthink = time + 0.1;
+       }
+}
+
+void Monster_DropItem (string itype, string itemsize)
+{
+       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 dprint("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 dprint("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 dprint("Invalid monster drop item selected.\n");
+       }
+       else
+       {
+               dprint("Invalid monster drop item selected.\n");
+               self = oldself;
+               return;
+       }
+       
+       self.gravity = 1;
+       setorigin(self, backuporigin);
+       
+       self.velocity = randomvec() * 175 + '0 0 325';
+       
+       self.touch = M_Item_Touch;
+       
+       SUB_SetFade(self, time + 5, 1);
+       
+       self = oldself;
+}
+
+float monster_isvalidtarget (entity targ, entity ent)
+{
+       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) >= ent.target_range)
+               return FALSE; // enemy is too far away
+
+       if(trace_ent != targ)
+               return FALSE; // we can't see the enemy
+               
+       if(targ.takedamage == DAMAGE_NO)
+               return FALSE; // enemy can't be damaged
+               
+       if(targ.items & IT_INVISIBILITY)
+               return FALSE; // enemy is invisible
+       
+       if(IS_SPEC(targ) || IS_OBSERVER(targ))
+               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 not(IsDifferentTeam(targ, ent))
+               return FALSE; // enemy is on our team
+       
+       return TRUE;
+}
+
+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))
+               {
+                       return e;
+               }
+       }
+       return world;
+}
+
+void MonsterTouch ()
+{
+       if(other == world)
+               return;
+               
+       if(self.enemy != other)
+       if not(other.flags & FL_MONSTER)
+       if(monster_isvalidtarget(other, self))
+               self.enemy = other;
+}
+
+void monster_sound(string msound, float sound_delay, float delaytoo)
+{
+       if(delaytoo && time < self.msound_delay)
+               return; // too early
+               
+       if(msound == "")
+               return; // sound doesn't exist
+
+       sound(self, CH_PAIN_SINGLE, msound, VOL_BASE, ATTN_NORM);
+
+       self.msound_delay = time + sound_delay;
+}
+
+void monster_precachesounds()
+{
+       precache_sound(self.msound_idle);
+       precache_sound(self.msound_death);
+       precache_sound(self.msound_attack_melee);
+       precache_sound(self.msound_attack_ranged);
+       precache_sound(self.msound_sight);
+       precache_sound(self.msound_pain);
+}
+
+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;
+       
+       Damage(targ, self, self, bigdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
+}
+
+void Monster_CheckDropCvars (string mon)
+{
+       if not(self.candrop)
+               return; // forced off
+       
+       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 r = random() * 4, chance = random() * 100;
+
+       // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
+       if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
+       {
+               self.health += autocvar_g_monsters_miniboss_healthboost;
+               ScaleMonster(1.5);
+               self.flags |= MONSTERFLAG_MINIBOSS;
+               
+               if (r < 2 || self.team == NUM_TEAM_2)
+               {
+                       self.strength_finished = -1;  
+                       self.effects |= (EF_FULLBRIGHT | EF_BLUE);
+               }
+               else if (r >= 1 || self.team == NUM_TEAM_1)
+               {
+                       self.invincible_finished = -1;
+                       self.effects |= (EF_FULLBRIGHT | EF_RED);
+               }
+               else
+                       self.effects |= (EF_FULLBRIGHT | EF_RED | EF_BLUE);
+               
+               if(teamplay)
+               if(self.team)
+                       return;
+                       
+               self.colormod = randomvec() * 4;
+       }
+}
+
+float Monster_CanRespawn(entity ent)
+{
+       other = ent;
+       if(MUTATOR_CALLHOOK(MonsterRespawn))
+               return TRUE; // enabled by a mutator
+               
+       if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
+               return FALSE;
+               
+       if not(autocvar_g_monsters_respawn)
+               return FALSE;
+               
+       return TRUE;
+}
+
+void Monster_Fade ()
+{
+       if(Monster_CanRespawn(self))
+       {
+               self.monster_respawned = TRUE;
+               setmodel(self, "");
+               self.think = self.monster_spawnfunc;
+               self.nextthink = time + self.respawntime;
+               setorigin(self, self.pos1);
+               self.angles = self.pos2;
+               self.health = self.max_health; // TODO: check if resetting to max_health is wise here
+               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(self.attack_melee)
+       if(vlen(self.enemy.origin - self.origin) <= 100 * self.scale)
+       {
+               monster_sound(self.msound_attack_melee, 0, FALSE); // no delay for attack sounds
+               self.attack_melee(); // don't wait for nextthink - too slow
+               return TRUE;
+       }
+       
+       // monster doesn't have a ranged attack function, so stop here
+       if not(self.attack_ranged)
+               return FALSE;
+
+       // see if any entities are in the way of the shot
+       if not(findtrajectorywithleading(self.origin, '0 0 0', '0 0 0', self.enemy, 800, 0, 2.5, 0, self))
+               return FALSE;
+
+       if(self.attack_ranged())
+       {
+               monster_sound(self.msound_attack_ranged, 0, FALSE); // no delay for attack sounds
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+void monster_use ()
+{
+       if (self.enemy)
+               return;
+       if (self.health <= 0)
+               return;
+
+       if(!monster_isvalidtarget(activator, self))
+               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;
+       }
+       if(targ)
+       {
+               self.monster_movestate = MONSTER_MOVE_WANDER;
+               return targ.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;
+                               
+                       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);
+               
+       entity targ;
+
+       if(self.frozen)
+       {
+               self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
+               self.health = max(1, self.max_health * self.revive_progress);
+               
+               if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health);
+                       
+               movelib_beak_simple(stopspeed);
+                       
+               self.velocity = '0 0 0';
+               self.enemy = world;
+               self.nextthink = time + 0.1;
+               
+               if(self.revive_progress >= 1)
+                       Unfreeze(self); // wait for next think before attacking
+                       
+               return; // no moving while frozen
+       }
+       
+       if(self.flags & FL_SWIM)
+       {
+               if(self.waterlevel < WATERLEVEL_WETFEET)
+               {
+                       if(time >= self.last_trace)
+                       {
+                               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;
+                               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;
+       }
+       
+       targ = self.goalentity;
+       
+       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 not(monster_isvalidtarget(self.enemy, self))
+               self.enemy = world; // check enemy each think frame?
+               
+       if not(self.enemy)
+       {
+               self.enemy = FindTarget(self);
+               if(self.enemy)
+                       monster_sound(self.msound_sight, 0, FALSE);
+       }
+               
+       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);
+       }
+
+       if not(self.enemy)
+               monster_sound(self.msound_idle, 5, TRUE);
+       
+       vector angles_face = vectoangles(self.moveto - self.origin);
+       vector owner_face = vectoangles(self.monster_owner.origin - self.origin);
+       vector enemy_face = vectoangles(self.enemy.origin - 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);
+       
+       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'); 
+       
+       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)
+                               self.angles_y = enemy_face_y;
+                       else
+                               self.angles_y = ((self.monster_owner) ? owner_face_y : self.pos2_y); // reset looking angle now?
+               }
+       }
+               
+       if(self.enemy && self.checkattack)
+               self.checkattack();
+}
+
+void monsters_setstatus()
+{
+       self.stat_monsters_total = monsters_total;
+       self.stat_monsters_killed = monsters_killed;
+}
+
+void Monster_Appear ()
+{
+       self.enemy = activator;
+       self.spawnflags &~= MONSTERFLAG_APPEAR;
+       self.monster_spawnfunc();
+}
+
+void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if(self.frozen && deathtype != DEATH_KILL)
+               return;
+               
+       if(time < self.pain_finished && deathtype != DEATH_KILL)
+               return;
+               
+       if((ignore_turrets && !(attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)) || !ignore_turrets)
+       if(monster_isvalidtarget(attacker, self))
+               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
+       
+       self.velocity += force * self.damageforcescale;
+               
+       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(deathtype == DEATH_KILL)
+                       self.candrop = FALSE; // killed by mobkill command
+                       
+               activator = attacker;
+               other = self.enemy;
+               self.target = self.target2;
+               self.target2 = "";
+               SUB_UseTargets();
+       
+               self.monster_die();
+               
+               frag_attacker = attacker;
+               frag_target = self;
+               MUTATOR_CALLHOOK(MonsterDies);
+       }
+}
+
+// used to hook into monster post death functions without a mutator
+void monster_hook_death()
+{
+       if(self.sprite)
+        WaypointSprite_Kill(self.sprite);
+               
+       monster_sound(self.msound_death, 0, FALSE);
+               
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
+               monsters_killed += 1;
+               
+       if(self.candrop && self.weapon)
+               W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');     
+               
+       if(self.flags & MONSTERFLAG_MINIBOSS && self.candrop && !self.weapon)
+               W_ThrowNewWeapon(self, WEP_NEX, 0, self.origin, randomvec() * 150 + '0 0 325');
+               
+       if(self.realowner.classname == "monster_spawner")
+               self.realowner.spawner_monstercount -= 1;
+               
+       if(IS_CLIENT(self.realowner))
+               self.realowner.monstercount -= 1;
+               
+       totalspawned -= 1;
+}
+
+// used to hook into monster post spawn functions without a mutator
+void monster_hook_spawn()
+{
+       Monster_CheckMinibossFlag();
+
+       self.max_health = self.health;
+       self.pain_finished = self.nextthink;
+
+       monster_precachesounds();
+       
+       if(teamplay && self.team)
+       {
+               self.colormod = Team_ColorRGB(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, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));    
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
+       
+       monster_sound(self.msound_spawn, 0, FALSE);
+
+       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(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; }
+       if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; }
+       if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; }
+       if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; }
+       if(monster_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 != "" && IS_PLAYER(self.realowner))
+                       net_name = strzone(strdecolorize(sprintf("%s's %s", self.realowner.netname, net_name)));
+               self.netname = ((net_name == "") ? self.classname : net_name);
+       }
+       
+       if not(self.scale)
+               self.scale = 1;
+       
+       if(self.spawnflags & MONSTERFLAG_GIANT && !autocvar_g_monsters_nogiants)
+               ScaleMonster(5);
+       else
+               ScaleMonster(self.scale);
+               
+       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;
+       self.candrop                    = TRUE;
+       
+       if not(self.target_range)
+               self.target_range = autocvar_g_monsters_target_range;
+       
+       if not(self.respawntime)
+               self.respawntime = autocvar_g_monsters_respawn_delay;
+       
+       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;
+}
diff --git a/qcsrc/server/monsters/lib/spawn.qc b/qcsrc/server/monsters/lib/spawn.qc
new file mode 100644 (file)
index 0000000..d9d68f1
--- /dev/null
@@ -0,0 +1,71 @@
+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(IS_CLIENT(spawnedby))
+                       Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_MONSTERS_DISABLED);
+                       
+               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, autocvar_g_monsters_spawn_list))
+               monster = "knight";
+       
+       e.realowner = spawnedby;
+       
+       if(moveflag)
+               e.monster_moveflags = moveflag;
+       
+       if (spawnedby.classname == "monster_swarm")
+               e.monster_owner = own;  
+       else if(IS_CLIENT(spawnedby))
+       {
+               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;
+}
diff --git a/qcsrc/server/monsters/monster/demon.qc b/qcsrc/server/monsters/monster/demon.qc
new file mode 100644 (file)
index 0000000..7f0f4bc
--- /dev/null
@@ -0,0 +1,133 @@
+// 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_FIEND);
+}
+
+void Demon_JumpTouch ()
+{
+       if (self.health <= 0)
+               return;
+               
+       float bigdmg = autocvar_g_monster_demon_attack_jump_damage * self.scale;
+
+       if (monster_isvalidtarget(other, self))
+       {
+               if (vlen(self.velocity) > 300)
+               {
+                       Damage(other, self, self, bigdmg * monster_skill, DEATH_MONSTER_FIEND, 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;
+       
+       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(); }
diff --git a/qcsrc/server/monsters/monster/dog.qc b/qcsrc/server/monsters/monster/dog.qc
new file mode 100644 (file)
index 0000000..33f449a
--- /dev/null
@@ -0,0 +1,122 @@
+// 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.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;
+       }
+}
diff --git a/qcsrc/server/monsters/monster/enforcer.qc b/qcsrc/server/monsters/monster/enforcer.qc
new file mode 100644 (file)
index 0000000..0449aea
--- /dev/null
@@ -0,0 +1,204 @@
+// size
+const vector ENFORCER_MIN = '-32 -32 0';
+const vector ENFORCER_MAX = '32 32 64';
+
+// 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_stop             0
+#define enforcer_anim_walk             1
+#define enforcer_anim_run              2
+#define enforcer_anim_walkback         3
+#define enforcer_anim_runback  4
+
+void enforcer_think ()
+{
+       self.think = enforcer_think;
+       self.nextthink = time + 0.1;
+       
+       if(self.delay != -1)
+               self.nextthink = self.delay;
+       
+       monster_move(autocvar_g_monster_enforcer_speed_run, autocvar_g_monster_enforcer_speed_walk, 100, enforcer_anim_run, enforcer_anim_walk, enforcer_anim_stop);
+}
+
+void enforcer_laser ()
+{
+       self.attack_finished_single = time + 0.8;
+       W_Laser_Attack(0);
+}
+
+float enf_missile_laser ()
+{
+       enforcer_laser();
+       return TRUE;
+}
+
+void enforcer_shotgun ()
+{
+       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);
+       self.delay = time + 0.1;
+       self.monster_delayedattack = enforcer_uzi_fire;
+}
+
+void enforcer_uzi ()
+{
+       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.attack_finished_single = time + 0.8;
+       W_Rocket_Attack();
+}
+
+float enf_missile_rocket ()
+{
+       enforcer_rl();
+       return TRUE;
+}
+
+void enforcer_electro ()
+{
+       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;
+       
+       remove(self.weaponentity);
+       self.weaponentity = world;
+               
+       self.frame = enforcer_anim_stop;
+               
+       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              = 45 * self.scale;
+       
+       self.weaponentity = spawn();
+       self.weaponentity.owner = self;
+       self.weaponentity.team = self.team;
+       self.weaponentity.solid = SOLID_NOT;
+       self.weaponentity.owner = self.weaponentity.realowner = self;
+       self.weaponentity.movetype = MOVETYPE_NOCLIP;
+       setmodel(self.weaponentity, "models/turrets/ewheel-gun1.md3");
+       setattachment(self.weaponentity, self, "tag_head");
+       
+       local float r = random();
+       if (r < 0.20)
+       {
+               self.attack_ranged = enf_missile_rocket;
+               self.weapon = WEP_ROCKET_LAUNCHER;
+       }
+       else if (r < 0.40)
+       {
+               self.attack_ranged = enf_missile_plasma;
+               self.weapon = WEP_ELECTRO;
+       }
+       else if (r < 0.60)
+       {
+               self.attack_ranged = enf_missile_shotgun;
+               self.weapon = WEP_SHOTGUN;
+       }
+       else if (r < 0.80)
+       {
+               self.attack_ranged = enf_missile_uzi;
+               self.weapon = WEP_UZI;
+       }
+       else
+       {
+               self.attack_ranged = enf_missile_laser;
+               self.weapon = WEP_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;
+       }
+       
+       if not (monster_initialize(
+                        "Enforcer",
+                        "models/turrets/ewheel-base2.md3",
+                        ENFORCER_MIN, ENFORCER_MAX,
+                        FALSE,
+                        enforcer_die, enforcer_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
diff --git a/qcsrc/server/monsters/monster/fish.qc b/qcsrc/server/monsters/monster/fish.qc
new file mode 100644 (file)
index 0000000..def5860
--- /dev/null
@@ -0,0 +1,94 @@
+// 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);
+}
+
+void fish_die ()
+{
+       Monster_CheckDropCvars ("fish");
+       
+       self.solid                      = SOLID_NOT;
+       self.takedamage         = DAMAGE_NO;
+       self.event_damage   = func_null;
+       self.enemy                      = world;
+       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;
+       }
+}
diff --git a/qcsrc/server/monsters/monster/hknight.qc b/qcsrc/server/monsters/monster/hknight.qc
new file mode 100644 (file)
index 0000000..3590495
--- /dev/null
@@ -0,0 +1,472 @@
+// 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, DEATH_MONSTER_HKNIGHT_SPIKE, 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.flags = FL_PROJECTILE;
+       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(vlen(self.enemy.origin - self.origin) <= 2000)
+               Fire_AddDamage(self.enemy, self, autocvar_g_monster_hellknight_inferno_damage * monster_skill, autocvar_g_monster_hellknight_inferno_damagetime, DEATH_MONSTER_HKNIGHT_INFERNO);
+}
+
+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, self.projectiledeathtype, 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;
+       self.projectiledeathtype = DEATH_MONSTER_HKNIGHT_FBALL;
+       setsize (missile, fmins, fmaxs);                
+       setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
+       missile.flags = FL_PROJECTILE;
+       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_HKNIGHT_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_HKNIGHT_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_HKNIGHT_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_HKNIGHT_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_HKNIGHT_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", ((vlen(self.enemy.origin - self.origin) > 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 (vlen(self.enemy.origin - self.origin) >= 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_MONSTER_HKNIGHT_CRUSH, 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;
+       
+       if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS)
+       if(self.candrop)
+       {
+               self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon
+               self.weapon = WEP_FIREBALL;
+       }
+       
+       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(); }
diff --git a/qcsrc/server/monsters/monster/knight.qc b/qcsrc/server/monsters/monster/knight.qc
new file mode 100644 (file)
index 0000000..64c3622
--- /dev/null
@@ -0,0 +1,103 @@
+// 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_KNIGHT);
+}
+
+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;
+       
+       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;
+       }
+}
diff --git a/qcsrc/server/monsters/monster/ogre.qc b/qcsrc/server/monsters/monster/ogre.qc
new file mode 100644 (file)
index 0000000..868a15d
--- /dev/null
@@ -0,0 +1,191 @@
+// 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 (vlen(self.enemy.origin - self.origin) > 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 <= 2)
+               chainsaw(200);
+       else if(self.ogre_cycles <= 4)
+               chainsaw(-200);
+       else
+               chainsaw(0);
+       
+       if(self.ogre_cycles >= 4)
+               self.think = ogre_think;
+}
+
+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_UZI);
+       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;
+       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.movetype           = MOVETYPE_TOSS;
+       self.think                      = Monster_Fade;
+       
+       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;
+       self.weapon                             = WEP_GRENADE_LAUNCHER;
+       
+       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);
+}
diff --git a/qcsrc/server/monsters/monster/shalrath.qc b/qcsrc/server/monsters/monster/shalrath.qc
new file mode 100644 (file)
index 0000000..4a523ae
--- /dev/null
@@ -0,0 +1,251 @@
+// 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_speed;
+float autocvar_g_monster_shalrath_attack_spike_damage;
+float autocvar_g_monster_shalrath_attack_spike_radius;
+float autocvar_g_monster_shalrath_attack_spike_delay;
+float autocvar_g_monster_shalrath_attack_melee_damage;
+float autocvar_g_monster_shalrath_attack_melee_delay;
+
+// animations
+#define shalrath_anim_idle             0
+#define shalrath_anim_walk             1
+#define shalrath_anim_attack   2
+#define shalrath_anim_pain             3
+#define shalrath_anim_death    4
+#define shalrath_anim_run              5
+
+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_run, shalrath_anim_idle);
+}
+
+void shalrath_attack ()
+{
+       self.frame = shalrath_anim_attack;
+       self.delay = time + 0.2;
+       self.attack_finished_single = time + autocvar_g_monster_shalrath_attack_spike_delay;
+       self.monster_delayedattack = ShalMissile;
+}
+
+void shalrathattack_melee ()
+{
+       float bigdmg = 0, rdmg = autocvar_g_monster_shalrath_attack_melee_damage * random();
+
+       bigdmg = rdmg * self.scale;
+
+       monster_melee(self.enemy, bigdmg * monster_skill, 120, DEATH_MONSTER_MAGE);
+}
+
+void shalrath_attack_melee ()
+{
+       self.monster_delayedattack = shalrathattack_melee;
+       self.delay = time + 0.2;
+       self.frame = shalrath_anim_attack;
+       self.attack_finished_single = time + autocvar_g_monster_shalrath_attack_melee_delay;
+}
+
+float shal_missile ()
+{
+       shalrath_attack();
+       
+       return TRUE;
+}
+
+void ShalHome ()
+{
+       local vector dir = '0 0 0', vtemp = self.enemy.origin + '0 0 10';
+       
+       if (self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
+       {
+               remove(self);
+               return;
+       }
+       dir = normalize(vtemp - self.origin);
+       UpdateCSQCProjectile(self);
+       if (monster_skill == 3)
+               self.velocity = dir * 350;
+       else
+               self.velocity = dir * 250;
+       self.nextthink = time + 0.2;
+       self.think = ShalHome;  
+}
+
+void shal_spike_explode ()
+{
+       self.event_damage = func_null;
+
+       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+       RadiusDamage (self, self.realowner, autocvar_g_monster_shalrath_attack_spike_damage, autocvar_g_monster_shalrath_attack_spike_damage * 0.5, autocvar_g_monster_shalrath_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other);
+
+       remove (self);
+}
+
+void shal_spike_touchexplode()
+{
+       PROJECTILE_TOUCH;
+
+       shal_spike_explode();
+}
+
+void ShalMissile ()
+{
+       local   entity  missile = world;
+       local   vector  dir = '0 0 0';
+       local   float   dist = 0;
+       
+       self.effects |= EF_MUZZLEFLASH;
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       
+       self.v_angle = self.angles;
+       makevectors (self.angles);
+       
+       dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       dist = vlen (self.enemy.origin - self.origin);
+
+       missile.think = ShalHome;
+       missile.ltime = time + 7;
+       missile.nextthink = time;
+       missile.solid = SOLID_BBOX;
+       missile.movetype = MOVETYPE_FLYMISSILE;
+       missile.flags = FL_PROJECTILE;
+       setorigin (missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
+       setsize (missile, '0 0 0', '0 0 0');    
+       missile.velocity = dir * 400;
+       missile.avelocity = '300 300 300';
+       missile.enemy = self.enemy;
+       missile.touch = shal_spike_touchexplode;
+       
+       CSQCProjectile(missile, TRUE, PROJECTILE_VORE_SPIKE, TRUE);
+}
+
+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)
+               {
+                       monster_sound(self.msound_attack_melee, 0, FALSE); // no delay for attack sounds
+                       self.attack_melee();
+                       return TRUE;
+               }
+       }
+
+// 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 && 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.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(
+                        "Mage",
+                        "models/monsters/mage.dpm",
+                        SHALRATH_MIN, SHALRATH_MAX,
+                        FALSE,
+                        shalrath_die, shalrath_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_vore () { spawnfunc_monster_shalrath(); }
diff --git a/qcsrc/server/monsters/monster/shambler.qc b/qcsrc/server/monsters/monster/shambler.qc
new file mode 100644 (file)
index 0000000..7125e48
--- /dev/null
@@ -0,0 +1,203 @@
+// 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 (vlen(self.enemy.origin - self.origin) > 100 * self.scale)
+               return;
+       
+       Damage(self.enemy, self, self, bigdmg * monster_skill, DEATH_MONSTER_SHAMBLER_SMASH, 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_ZAP);
+       
+       // 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");
+       
+       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.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;
+       self.weapon                             = WEP_NEX;
+       
+       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");
+}
diff --git a/qcsrc/server/monsters/monster/soldier.qc b/qcsrc/server/monsters/monster/soldier.qc
new file mode 100644 (file)
index 0000000..4747067
--- /dev/null
@@ -0,0 +1,358 @@
+// 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_die1                      0
+#define soldier_anim_die2                      1
+#define soldier_anim_draw                      2
+#define soldier_anim_duck                      3
+#define soldier_anim_duckwalk          4
+#define soldier_anim_duckjump          5
+#define soldier_anim_duckidle          6
+#define soldier_anim_idle                      7
+#define soldier_anim_jump                      8
+#define soldier_anim_pain1                     9
+#define soldier_anim_pain2                     10
+#define soldier_anim_shoot                     11
+#define soldier_anim_taunt                     12
+#define soldier_anim_run                       13
+#define soldier_anim_runbackwards      14
+#define soldier_anim_strafeleft        15
+#define soldier_anim_straferight       16
+#define soldier_anim_dead1                     17
+#define soldier_anim_dead2                     18
+#define soldier_anim_forwardright      19
+#define soldier_anim_forwardleft       20
+#define soldier_anim_backright                 21
+#define soldier_anim_backleft          22
+
+//#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_run, soldier_anim_idle);
+}
+
+void soldier_reload ()
+{
+       self.frame = soldier_anim_draw;
+       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;
+
+       // 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 (vlen(self.enemy.origin - self.origin) >= 2000)
+               return FALSE;
+
+       if (vlen(self.enemy.origin - self.origin) <= 120)
+               chance = 0.9;
+       else if (vlen(self.enemy.origin - self.origin) <= 500)
+               chance = 0.6; // was 0.4
+       else if (vlen(self.enemy.origin - self.origin) <= 1000)
+               chance = 0.3; // was 0.05
+       else
+               chance = 0;
+
+       if (chance > 0)
+       if (chance > random())
+               return FALSE;
+               
+       if(self.currentammo <= 0 && vlen(self.enemy.origin - self.origin) <= 120)
+       {
+               monster_sound(self.msound_attack_melee, 0, FALSE); // no delay for attack sounds
+               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_MARINE_UZI);
+       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_MARINE_SLAP);
+}
+
+void soldier_die()
+{
+       Monster_CheckDropCvars ("soldier");
+       
+       remove(self.weaponentity);
+       
+       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.weaponentity       = world;
+
+       if (random() < 0.5)
+               self.frame = soldier_anim_die1;
+       else
+               self.frame = soldier_anim_die2;
+               
+       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.frame                              = soldier_anim_draw;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = soldier_think;
+       self.sprite_height              = 45 * 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);
+       
+       self.weaponentity = spawn();
+       self.weaponentity.movetype = MOVETYPE_NOCLIP;
+       self.weaponentity.team = self.team;
+       self.weaponentity.solid = SOLID_NOT;
+       self.weaponentity.owner = self.weaponentity.realowner = self;
+       setmodel(self.weaponentity, "models/weapons/v_seeker.md3");
+       setattachment(self.weaponentity, self, "bip01 r hand");
+       
+       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;
+       }
+       
+       if not (monster_initialize(
+                        "Grunt",
+                        "models/monsters/soldier.zym",
+                        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(); }
diff --git a/qcsrc/server/monsters/monster/spawner.qc b/qcsrc/server/monsters/monster/spawner.qc
new file mode 100644 (file)
index 0000000..d7a5ca2
--- /dev/null
@@ -0,0 +1,140 @@
+// 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 spawnmonsters ()
+{
+       if(self.spawner_monstercount >= autocvar_g_monster_spawner_maxmobs || self.frozen)
+               return;
+               
+       vector p1, p2, p3, p4, chosenposi;
+       float r = random();
+       string type = self.spawnmob;
+       entity e;
+       
+       self.spawner_monstercount += 1;
+               
+       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 < 0.80)
+               chosenposi = p3;
+       else
+               chosenposi = p4;
+
+       e = spawnmonster(type, self, self, chosenposi, FALSE, MONSTER_MOVE_WANDER);
+       
+       e.team = self.team;
+       e.candrop = FALSE;
+       
+       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() 
+{
+       self.think = spawner_think;
+       
+       if(self.spawner_monstercount >= autocvar_g_monster_spawner_maxmobs)
+               self.nextthink = time + 15;
+
+       if (self.spawner_monstercount <= autocvar_g_monster_spawner_maxmobs)
+               spawnmonsters();
+
+       self.nextthink = time + 1;
+
+       if(self.spawner_monstercount <= autocvar_g_monster_spawner_maxmobs)
+               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;
+       
+       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");
+}
diff --git a/qcsrc/server/monsters/monster/spider.qc b/qcsrc/server/monsters/monster/spider.qc
new file mode 100644 (file)
index 0000000..6e88618
--- /dev/null
@@ -0,0 +1,225 @@
+// 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.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;
+
+       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_SPIDER, self.origin, '0 0 0');
+               
+       if(random() < 0.50)
+               self.frame = spider_anim_attack;
+       else
+               self.frame = spider_anim_attack2;
+
+       self.attack_finished_single = 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.attack_finished_single = 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.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;
+       }
+}
diff --git a/qcsrc/server/monsters/monster/tarbaby.qc b/qcsrc/server/monsters/monster/tarbaby.qc
new file mode 100644 (file)
index 0000000..414860c
--- /dev/null
@@ -0,0 +1,157 @@
+// 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_MONSTER_TARBABY, 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, 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(); }
diff --git a/qcsrc/server/monsters/monster/wizard.qc b/qcsrc/server/monsters/monster/wizard.qc
new file mode 100644 (file)
index 0000000..31d8267
--- /dev/null
@@ -0,0 +1,183 @@
+// 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;
+       
+       self.attack_finished_single = time + 0.2;
+
+       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;
+       missile.projectiledeathtype = DEATH_MONSTER_SCRAG;
+       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';
+       missile.projectiledeathtype = DEATH_MONSTER_SCRAG;
+       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.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(); }
diff --git a/qcsrc/server/monsters/monster/zombie.qc b/qcsrc/server/monsters/monster/zombie.qc
new file mode 100644 (file)
index 0000000..2574175
--- /dev/null
@@ -0,0 +1,218 @@
+/**
+ * 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;
+       
+       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_ZOMBIE_MELEE, self.origin, '0 0 0');
+       }
+       
+       if(self.enemy.health < 1)
+               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_ZOMBIE_JUMP, 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.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);
+       
+       // some sounds
+       if(self.msound_idle == "") self.msound_idle = "monsters/zombie_idle.wav";
+       if(self.msound_death == "") self.msound_death = "monsters/zombie_death.wav";
+       if(self.msound_pain == "") self.msound_pain = "monsters/zombie_pain.wav";
+       if(self.msound_attack_melee == "") self.msound_attack_melee = "monsters/zombie_melee.wav";
+       if(self.msound_attack_ranged == "") self.msound_attack_ranged = "monsters/zombie_attack.wav";
+       if(self.msound_sight == "") self.msound_sight = "monsters/zombie_sight.wav";
+       
+       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;
+       }
+}
diff --git a/qcsrc/server/monsters/monsters.qh b/qcsrc/server/monsters/monsters.qh
new file mode 100644 (file)
index 0000000..a969549
--- /dev/null
@@ -0,0 +1,21 @@
+// 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"
index 9b9f7fde01abe5dc22dcd51310665c265aa15e6d..633b0760917fa7438a592c1769140638ccf82e14 100644 (file)
@@ -102,6 +102,19 @@ void movelib_move(vector force,float max_velocity,float drag,float theMass,float
             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;
index 35b2e652349a3f95ac238df57e083082537c62ab..0dca24019b871cc9be3d1de752c101cd965ed81f 100644 (file)
@@ -105,6 +105,16 @@ MUTATOR_HOOKABLE(TurretSpawn);
        // return error to request removal
        // INPUT: self - turret
        
+MUTATOR_HOOKABLE(TurretDies);
+       // called when a turret dies
+       
+MUTATOR_HOOKABLE(TurretValidateTarget);
+       // return target score
+       // INPUT:
+               entity turret_target;
+               entity turret;
+               float turret_flags;
+       
 MUTATOR_HOOKABLE(OnEntityPreSpawn);
        // return error to prevent entity spawn, or modify the entity
 
@@ -129,6 +139,38 @@ MUTATOR_HOOKABLE(EditProjectile);
        // 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(MonsterRespawn);
+       // called when a monster wants to respawn
+       // INPUT:
+               entity other;
+               
+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.
@@ -235,6 +277,9 @@ MUTATOR_HOOKABLE(HelpMePing);
        // 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
diff --git a/qcsrc/server/mutators/gamemode_rts.qc b/qcsrc/server/mutators/gamemode_rts.qc
new file mode 100644 (file)
index 0000000..874aacc
--- /dev/null
@@ -0,0 +1,435 @@
+// Real-Time Strategy
+// Gamemode by Mario
+
+// basically a fusion reactor with a new classname
+void spawnfunc_healing_tower()
+{
+       self.spawnflags = TSL_NO_RESPAWN; // healing towers don't respawn?
+       self.netname = "Monster Healing Tower"; // not used by waypoints...
+       spawnfunc_turret_fusionreactor();
+       self.classname = "healing_tower";
+       self.target_range = 1000;
+       self.shot_dmg = 30;
+}
+
+void rts_waypoint_think()
+{
+       float goalcount = 0;
+       entity e;
+       
+       self.nextthink = time + 0.1;
+       
+       for(e = world; (e = findentity(e, goalentity, self)); )
+       {
+               ++goalcount;
+       }
+       
+       if(goalcount < 1)
+       {
+               WaypointSprite_Kill(self.sprite);
+               remove(self);
+               return;
+       }
+}
+
+void Monster_LevelUp(entity e)
+{
+       if(self.level >= 5)
+               return; // max level is 5 for now
+       e.speed += 0.25;
+       e.max_health += 20;
+       e.health = e.max_health;
+       e.level += 1;
+       WaypointSprite_UpdateHealth(e.sprite, e.health);
+}
+
+MUTATOR_HOOKFUNCTION(rts_PlayerSpawn)
+{
+       if(self.rts_viewangle)
+               self.angles_x = self.rts_viewangle;
+       else
+               self.angles_x = 30;
+               
+       self.effects |= EF_NODRAW;
+       self.oldorigin = self.origin;
+       self.monster_attack = FALSE;
+       self.last_click = time;
+       self.takedamage = DAMAGE_NO;
+       self.flags |= FL_NOTARGET;
+       self.movetype = MOVETYPE_NOCLIP;
+       stuffcmd(self, "cl_cmd settemp cl_prydoncursor 1\n");
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_FilterItem)
+{
+       // no items... yet
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_SetStartItems)
+{
+       WEPSET_COPY_AW(start_weapons, 0);
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_PlayerThink)
+{
+       if(self.classname != "player")
+               return FALSE; // dont do any checks for spectators
+               
+       switch(self.impulse)
+       {
+               case 10:
+        case 15:        
+        case 18:
+            self.oldorigin_z += 50;
+            break;
+        case 12:
+        case 16:
+        case 19:
+                       self.oldorigin_z -= 50;
+                       break;
+       }
+       self.hasweapon_complain_spam = time + 9999999999; // no spam
+               
+       entity head, wp = world;
+       if(!self.cursor_trace_ent && self.BUTTON_ATCK && time >= self.last_click)
+       {       
+               FOR_EACH_MONSTER(head)
+               {
+                       if(head.owner != self) continue;
+                       
+                       head.selected = FALSE;
+                       
+                       if(!self.enemy)
+                               head.owner = world;
+               }
+       }
+       if(self.cursor_trace_ent.flags & FL_MONSTER && self.BUTTON_ATCK && time >= self.last_click)
+       {
+               if(self.cursor_trace_ent.owner != self && self.cursor_trace_ent.owner != world)
+                       return FALSE; // someone else owns it
+               else if(self.cursor_trace_ent.team != self.team)
+                       return FALSE; // not our team
+               else if(self.cursor_trace_ent.selected)
+               {
+                       self.cursor_trace_ent.selected = FALSE;
+                       self.cursor_trace_ent.owner = world;
+                       self.last_click = time + 0.5; // prevent spamming
+               }
+               else
+               {
+                       self.cursor_trace_ent.owner = self;
+                       self.cursor_trace_ent.selected = TRUE;
+                       self.last_click = time + 0.5; // prevent spamming
+               }
+       }
+       if(self.BUTTON_ATCK2)
+       {
+               entity e = self.cursor_trace_ent;
+               
+               if not(e)
+               {
+                       entity t;
+                       for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); )
+                       {
+                               if(vlen(self.cursor_trace_endpos - t.origin) < 80)
+                               {
+                                       if(IsDifferentTeam(e, t))
+                                       {
+                                               e = t;
+                                               break; // don't bother checking any other turrets
+                                       }
+                               }
+                       }
+               }
+               
+               if(e)
+               if not(e.takedamage)
+                       e = world;
+               
+               if not(e)
+               {
+                       wp = spawn();
+                       wp.classname = "monster_waypoint"; // set so we can kill this later
+                       wp.owner = self; // hmm...
+                       wp.think = rts_waypoint_think;
+                       wp.nextthink = time;
+                       WaypointSprite_Spawn("Here", 1, 0, wp, '0 0 10', world, self.team, wp, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? Team_ColorRGB(self.team) : '1 0 0'));
+                       setorigin(wp, self.cursor_trace_endpos);
+               }
+               
+               FOR_EACH_MONSTER(head)
+               {
+                       if(head.owner != self) continue;
+                       if not(head.selected) continue;
+                       
+                       if(e)
+                       {
+                               float sheight = ((e.sprite_height) ? e.sprite_height + 20 : 80);
+                               if(IsDifferentTeam(e, self))
+                               {
+                                       WaypointSprite_Spawn("Attacking", 1, 0, e, '0 0 1' * sheight, world, self.team, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? Team_ColorRGB(self.team) : '1 0 0'));
+                                       head.goalentity = world;
+                                       head.enemy = e;
+                               }
+                               else if(e.flags & FL_MONSTER)
+                               {
+                                       WaypointSprite_Spawn("Following", 1, 0, e, '0 0 1' * sheight, world, self.team, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? Team_ColorRGB(self.team) : '1 0 0'));
+                                       head.goalentity = e;
+                               }
+                               else // its not a monster or an enemy, so revert to waypoint
+                               {
+                                       head.goalentity = wp;
+                                       head.enemy = world;
+                               }
+                                       
+                       }
+                       else
+                       {
+                               head.goalentity = wp;
+                               head.enemy = world;
+                       }
+               }
+       }
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_MonsterSpawn)
+{
+       // new monster
+       if not(self.monster_respawned)
+       {
+               self.level = 0;
+               self.speed = 1;
+       }
+       
+       self.spawnflags = MONSTERFLAG_NORESPAWN;
+       
+       self.goalentity = world;
+       self.enemy = world;
+       self.moveto = self.origin;
+               
+       self.respawntime = 10; // default to 10 seconds for now
+       self.effects |= EF_SELECTABLE;
+       self.monster_moveflags = MONSTER_MOVE_NOMOVE;
+       
+       WaypointSprite_Kill(self.sprite);
+       self.sprite = world;
+       self.heal_delay = -1; // this is reset when monster takes damage
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_MonsterThink)
+{
+       vector color = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
+               
+       if(self.health >= self.max_health)
+               self.heal_delay = -1;   
+       else if(time >= self.heal_delay)
+       {
+               self.health = min(self.health + 5, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+               self.heal_delay = time + 2;
+       }
+               
+       monster_speed_run = 150 * self.speed;
+       monster_speed_walk = 150 * self.speed;
+       
+       if(monster_target.classname == "player")
+               monster_target = world;
+               
+       if not(IsDifferentTeam(monster_target, self))
+       {
+               // following a fellow teammate, so attack their enemy
+               if(monster_target.deadflag != DEAD_NO || monster_target.health < 1)
+                       monster_target = world; // teammate died
+                       
+               if(monster_target.enemy)
+               {
+                       self.enemy = monster_target.enemy;
+                       monster_target = world; // don't follow anymore?
+               }
+       }
+       
+       if(self.selected)
+               self.colormod = color * 10;
+       else
+               self.colormod = color;
+               
+       if(monster_target)
+               self.enemy = world; // don't ignore our owner's commands
+       
+       if not(self.sprite)
+       {
+               WaypointSprite_Spawn(self.netname, 0, 0, self, '0 0 1' * self.sprite_height, world, self.team, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? Team_ColorRGB(self.team) : '1 0 0'));
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
+       
+       if(self.owner)
+       if not(self.selected)
+               self.owner = world;
+       
+       if not(IsDifferentTeam(self, self.enemy))
+               self.enemy = world; // no same team fighting
+       
+       self.last_trace = time; // realtime moving?
+               
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_MonsterDies)
+{
+       entity e;
+       
+       if(IsDifferentTeam(frag_attacker, frag_target) && frag_attacker.team)
+               TeamScore_AddToTeam(frag_attacker.team, ST_SCORE, 1);
+       
+       // need to keep the monster selected to get the points... hmm (TODO: realowners?)
+       if(frag_attacker.owner.classname == "player")
+       {
+               PlayerScore_Add(frag_attacker.owner, SP_SCORE, 5);
+               PlayerScore_Add(frag_attacker.owner, SP_KILLS, 1);
+       }
+               
+       if(frag_attacker.flags & FL_MONSTER)
+       {
+               frag_attacker.monster_score += 5;
+               if(frag_attacker.monster_score == 25)
+                       Monster_LevelUp(frag_attacker);
+       }
+       
+       for(e = world; (e = findentity(e, goalentity, self)); )
+       {
+               e.goalentity = world; // fix teammates if they still see us as a valid target
+       }
+
+       self.effects &~= EF_SELECTABLE;
+       self.selected = FALSE;
+       
+       self.goalentity = world;
+       self.enemy = world;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_MonsterRespawn)
+{
+       if(other.team)
+               return TRUE;
+               
+       return FALSE; // if no team is set, don't respawn
+}
+
+MUTATOR_HOOKFUNCTION(rts_MonsterTarget)
+{
+       // don't search for enemies, they are given to us
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_MonsterBossFlag)
+{
+       // no minibosses in RTS
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_PlayerDamage)
+{
+       if(frag_target.classname == "player")
+               frag_damage = 0; // don't damage the invincible players...
+               
+       if((frag_target.flags & FL_MONSTER) && frag_target.goalentity)
+               frag_target.enemy = world; // don't attack the attacker, we're probably pulling back
+               
+       if((frag_target.flags & FL_MONSTER) && !IsDifferentTeam(frag_target, frag_attacker))
+               frag_damage = 0; // no team damage
+               
+       if((frag_target.flags & FL_MONSTER) && frag_damage > 0)
+               frag_target.heal_delay = time + 2; // reset delay whenever hurt
+               
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_PlayerPhysics)
+{
+       if(self.classname != "player")
+               return FALSE;
+               
+       self.origin_z = self.oldorigin_z;
+       self.stat_sv_maxspeed *= 4; // lol
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(rts_PlayerDies)
+{
+       // prevent changing teams with selected monsters
+       entity head;
+       FOR_EACH_MONSTER(head)
+       {
+               if(head.owner != self) continue;
+               if not(head.selected) continue;
+               
+               if(IsDifferentTeam(self, head))
+               {
+                       head.selected = FALSE;
+                       head.owner = world;
+               }
+       }
+       
+       return FALSE;
+}
+
+void rts_ScoreRules()
+{
+       ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
+       ScoreRules_basics_end();
+}
+
+void rts_DelayedInit()
+{
+       rts_ScoreRules();
+}
+
+void rts_Initialize()
+{
+       InitializeEntity(world, rts_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+MUTATOR_DEFINITION(gamemode_rts)
+{
+       MUTATOR_HOOK(PlayerPhysics, rts_PlayerPhysics, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerSpawn, rts_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SetStartItems, rts_SetStartItems, CBC_ORDER_ANY);
+       MUTATOR_HOOK(FilterItem, rts_FilterItem, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterSpawn, rts_MonsterSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPreThink, rts_PlayerThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterMove, rts_MonsterThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterFindTarget, rts_MonsterTarget, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterDies, rts_MonsterDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterRespawn, rts_MonsterRespawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterCheckBossFlag, rts_MonsterBossFlag, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDamage_Calculate, rts_PlayerDamage, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDies, rts_PlayerDies, 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");
+               
+               rts_Initialize();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               error("This is a game type and it cannot be removed at runtime.");
+       }
+
+       return FALSE;
+}
diff --git a/qcsrc/server/mutators/gamemode_rts.qh b/qcsrc/server/mutators/gamemode_rts.qh
new file mode 100644 (file)
index 0000000..c08a5c1
--- /dev/null
@@ -0,0 +1,7 @@
+.vector oldorigin;
+.float selected;
+.float last_click;
+.float heal_delay;
+.float monster_score;
+.float level;
+.float rts_viewangle;
\ No newline at end of file
diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qc b/qcsrc/server/mutators/gamemode_towerdefense.qc
new file mode 100644 (file)
index 0000000..d971d9e
--- /dev/null
@@ -0,0 +1,1072 @@
+// Tower Defense
+// Gamemode by Mario
+void spawnfunc_td_controller()
+{
+       if not(g_td) { remove(self); return; }
+       
+       if(autocvar_g_td_force_settings)
+       {
+               // TODO: find a better way to do this?
+               self.dontend = FALSE;
+               self.maxwaves = 0;
+               self.monstercount = 0;
+               self.startwave = 0;
+               self.maxturrets = 0;
+               self.buildtime = 0;
+               self.mspeed_walk = 0;
+               self.mspeed_run = 0;
+               self.spawndelay = 0;
+               self.maxcurrent = 0;
+               self.ignoreturrets = 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);
+       m_speed_walk = ((self.mspeed_walk) ? self.mspeed_walk : autocvar_g_td_monsters_speed_walk);
+       m_speed_run = ((self.mspeed_run) ? self.mspeed_run : autocvar_g_td_monsters_speed_run);
+       spawn_delay = ((self.spawndelay) ? self.spawndelay : autocvar_g_td_monsters_spawn_delay);
+       max_current = ((self.maxcurrent) ? self.maxcurrent : autocvar_g_td_current_monsters);
+       ignore_turrets = ((self.ignoreturrets) ? self.ignoreturrets : autocvar_g_td_monsters_ignore_turrets);
+       
+       if(autocvar_g_td_monsters_skill_start)
+               monster_skill = autocvar_g_td_monsters_skill_start;
+               
+       wave_end(TRUE);
+}
+
+void td_generator_die() 
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(":gendestroyed");
+               
+       gendestroyed = TRUE;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
+       
+       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(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
+               return;
+               
+       if (time > self.pain_finished)
+       {
+               self.pain_finished = time + 10;
+               play2all("onslaught/generator_underattack.wav");
+       }
+       
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_GENDAMAGED);
+       
+       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; }
+       
+       self.classname = "td_waypoint";
+}
+
+void spawnfunc_monster_swarm()
+{
+       if not(g_td) { remove(self); return; }
+       
+       self.flags = SWARM_NORMAL; // marked as a spawnpoint
+       self.classname = "monster_swarm";
+       
+       if(self.spawntype == SWARM_SWIM) waterspawns_count += 1;
+       if(self.spawntype == SWARM_FLY) flyspawns_count += 1;
+       
+       WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '0 0 1');
+       
+       if(self.target == "")
+               dprint("Warning: monster_swarm entity without a set target\n");
+}
+
+void barricade_touch()
+{
+       if not(other.flags & FL_MONSTER)
+               return;
+               
+       if(time < self.dmg_time)
+               return;
+               
+       Damage(other, self, self, autocvar_g_td_barricade_damage, DEATH_HURTTRIGGER, self.origin, '0 0 0');
+       
+       self.dmg_time = time + 1;
+}
+
+void barricade_die()
+{
+       self.takedamage = DAMAGE_NO;
+       self.event_damage = func_null;
+       
+       WaypointSprite_Kill(self.sprite);
+       
+       pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+       
+       if(self.realowner)
+               self.realowner.turret_cnt -= 1;
+               
+       self.think = SUB_Remove;
+       self.nextthink = time;
+}
+
+void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if not(attacker.flags & FL_MONSTER) return;
+       
+       self.health -= damage;
+       
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       
+       if(self.health < 1)
+               barricade_die();
+}
+
+void spawn_barricade()
+{
+       self.health = 2000;
+       self.max_health = self.health;
+       self.dmg_time = time;
+       self.touch = barricade_touch;
+       self.think = func_null;
+       self.nextthink = -1;
+       self.takedamage = DAMAGE_AIM;
+       self.turrcaps_flags = TFL_TURRCAPS_ISTURRET; // for turretremove commands etc.
+       self.solid = SOLID_CORPSE; // hax
+       self.event_damage = barricade_damage;
+       self.netname = "Barricade";
+       
+       WaypointSprite_Spawn(self.netname, 0, 1200, self, '0 0 110', world, 0, self, sprite, FALSE, RADARICON_DOMPOINT, '1 1 0');       
+       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       
+       precache_model("models/td/barricade.md3");
+       setmodel(self, "models/td/barricade.md3");
+       
+       droptofloor();
+       
+       self.movetype = MOVETYPE_NONE;
+}
+
+float td_checkfuel(entity ent, string tur)
+{
+       float turcost = cvar(strcat("g_td_turret_", tur, "_cost"));
+       
+       if(ent.ammo_fuel < turcost)
+       {
+               Send_Notification(NOTIF_ONE, ent, MSG_INFO, INFO_TD_NOFUEL);
+               return FALSE;
+       }
+       
+       ent.ammo_fuel -= turcost;
+       
+       return TRUE;
+}      
+
+void spawnturret(entity spawnedby, entity own, string turet, vector orig)
+{
+       if not(IS_PLAYER(spawnedby)) { dprint("Warning: A non-player entity tried to spawn a turret\n"); return; }
+       if not(td_checkfuel(spawnedby, turet)) { return; }
+               
+       entity oldself;
+       
+       oldself = self;
+       self = spawn();
+       
+       setorigin(self, orig);
+       self.spawnflags = TSL_NO_RESPAWN;
+       self.monster_attack = TRUE;
+       self.realowner = own;
+       self.playerid = own.playerid;
+       self.angles_y = spawnedby.v_angle_y;
+       spawnedby.turret_cnt += 1;
+       self.colormap = spawnedby.colormap;
+       
+       switch(turet)
+       {
+               case "plasma": spawnfunc_turret_plasma(); break;
+               case "mlrs": spawnfunc_turret_mlrs(); break;
+               case "walker": spawnfunc_turret_walker(); break;
+               case "flac": spawnfunc_turret_flac(); break;
+               case "towerbuff": spawnfunc_turret_fusionreactor(); break;
+               case "barricade": spawn_barricade(); break;
+               default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return;
+       }
+       
+       Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_SPAWN);
+               
+       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)
+{
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce);
+}
+
+entity PickSpawn (float strngth, float type)
+{
+       entity e;
+       RandomSelection_Init();
+       for(e = world;(e = find(e, classname, "monster_swarm")); )
+       {
+               if(flyspawns_count > 0 && type == SWARM_FLY && e.spawntype != SWARM_FLY) continue;
+               if(waterspawns_count > 0 && type == SWARM_SWIM && e.spawntype != SWARM_SWIM) continue;
+               
+               RandomSelection_Add(e, 0, string_null, 1, 1);
+       }
+
+       return RandomSelection_chosen_ent;
+}
+
+void TD_SpawnMonster(string mnster, float strngth, float type)
+{
+       entity e, mon;
+       
+       e = PickSpawn(strngth, type);
+       
+       if(e == world) // couldn't find anything for our class, so check for normal spawns
+               e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL);
+               
+       if(e == world)
+       {
+               dprint("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n");
+               return;
+       }
+  
+       mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
+       if(e.target2)
+       {
+               if(random() > 0.5)
+                       mon.target = e.target2;
+               else
+                       mon.target = e.target;
+       }
+       else
+               mon.target = e.target;
+}
+
+float 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 SWARM_NORMAL;
+       }
+}
+
+float 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 SWARM_NORMAL;
+               case "wizard":
+                       return SWARM_FLY;
+               case "fish":
+                       return SWARM_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 whichmon;
+       float mstrength, montype;
+       
+       current_phase = PHASE_COMBAT;
+       
+       if(monster_count <= 0)
+       {
+               wave_end(FALSE);
+               return;
+       }
+       
+       self.think = combat_phase;
+       
+       whichmon = RandomMonster();
+       
+       mstrength = Monster_GetStrength(whichmon);
+       montype = Monster_GetType(whichmon);
+       
+       if(current_monsters <= max_current && whichmon != "")
+       {
+               TD_SpawnMonster(whichmon, mstrength, montype);
+               self.nextthink = time + spawn_delay;
+       }
+       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 gen;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_COMBAT);
+       
+       if(autocvar_sv_eventlog)
+               GameLogEcho(":combatphase");
+               
+       self.think = combat_phase;
+       self.nextthink = time + 1;
+       
+       for(gen = world;(gen = find(gen, classname, "td_generator")); )
+               gen.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, mcount, mskill;
+       
+       current_phase = PHASE_BUILD;
+       
+       for(head = world;(head = find(head, classname, "td_generator")); )
+       {
+               if(head.health <= 15 && head.max_health > 100)
+                       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) head.health = 100;
+               if(gen_washealed) PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
+                       
+        n_players += 1;
+       }
+       
+       mcount = autocvar_g_td_monster_count_increment * wave_count;
+       mskill = n_players * 0.02;
+               
+       totalmonsters += mcount;
+       monster_skill += autocvar_g_td_monsters_skill_increment;
+       monster_skill += mskill;
+       
+       if(monster_skill < 1) monster_skill = 1;
+       if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
+       if(wave_count < 1) wave_count = 1;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_BUILD, wave_count, totalmonsters, autocvar_g_td_buildphase_time);
+    
+    FOR_EACH_MONSTER(head)
+    {
+               if(head.health <= 0)
+                       continue;
+                       
+        dprint(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
+               
+               WaypointSprite_Kill(head.sprite);
+        remove(head);
+    }
+       
+       monsters_total = totalmonsters;
+       monsters_killed = 0;
+               
+       queue_monsters(totalmonsters);
+       
+       cphase_updates = -1;
+       
+       if(autocvar_sv_eventlog)
+        GameLogEcho(strcat(":buildphase:", ftos(wave_count), ":", ftos(totalmonsters)));
+       
+       self.think = combat_phase_announce;
+       self.nextthink = time + build_time - 6;
+}
+
+void wave_end(float starting)
+{
+       if not(starting)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_VICTORY, ((wave_count >= max_waves) ? "Level" : "Wave"));
+               
+               if(autocvar_sv_eventlog)
+            GameLogEcho(strcat(":wave:", ftos(wave_count), ":victory"));
+       }
+       
+       if(wave_count >= max_waves)
+       {
+               gensurvived = TRUE;
+               return;
+       }
+       
+       if not(starting)
+               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_Initialize()
+{
+       InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
+{
+       if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
+    if(turret_target.flags & FL_PROJECTILE)
+       if(turret_target.owner.flags & FL_MONSTER)
+        return TRUE; // flac support
+                       
+       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)
+{
+       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 && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
+               frag_damage = 0;
+               
+       if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && IS_PLAYER(frag_target))
+               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;
+               
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretDies)
+{
+       if(self.realowner)
+               self.realowner.turret_cnt -= 1;
+                       
+       return FALSE;
+}
+
+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 not(self.enemy) // don't change targets while attacking
+       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"))
+       {
+               if(self.goalentity.target2)
+               {
+                       if(random() > 0.5)
+                               self.target = self.goalentity.target2;
+                       else
+                               self.target = self.goalentity.target;
+               }
+               else
+                       self.target = self.goalentity.target;
+                               
+               self.goalentity = find(world, targetname, self.target);
+               
+               if(self.goalentity == world)
+                       self.goalentity = PickGenerator();
+       }
+       
+       monster_speed_run = m_speed_run * monster_skill;
+       monster_speed_walk = m_speed_walk * monster_skill;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
+{
+       if(self.realowner == world) // nothing spawned it, so kill it
+       {
+               WaypointSprite_Kill(self.sprite);
+               remove(self);
+               return TRUE;
+       }
+       
+       current_monsters += 1;
+       
+       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;
+       
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_BODY;
+       
+       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(IS_PLAYER(frag_attacker))
+       {
+               PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
+               PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
+       }
+       else if(IS_PLAYER(frag_attacker.realowner))
+       {
+               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(ignore_turrets)
+               if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+                       continue;
+               
+               if(monster_isvalidtarget(e, self))
+               if((vlen(trace_endpos - self.origin) < 200 && 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_HITMODEL, self);
+       
+       if(cmd_name == "turretspawn")
+       {
+               if(argv(1) == "list")
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_LIST, "mlrs walker plasma towerbuff flac barricade");
+                       return TRUE;
+               }
+               if(!IS_PLAYER(self) || self.health <= 0)
+               { 
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_CANTSPAWN);
+                       return TRUE;
+               }
+               if(self.turret_cnt >= max_turrets)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXTURRETS, max_turrets);
+                       return TRUE;
+               }
+               
+               spawnturret(self, self, argv(1), trace_endpos);
+               
+               return TRUE;
+       }
+       if(cmd_name == "repairturret")
+       {
+               if((trace_ent.playerid != self.playerid || trace_ent.realowner != self) || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REPAIR);
+                       return TRUE;
+               }
+               if(self.ammo_fuel < autocvar_g_td_turret_repair_cost)   
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_REPAIR, autocvar_g_td_turret_repair_cost);
+                       return TRUE;
+               }
+               if(trace_ent.health >= trace_ent.max_health)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXHEALTH);
+                       return TRUE;
+               }
+               
+               self.ammo_fuel -= autocvar_g_td_turret_repair_cost;
+               trace_ent.SendFlags |= TNSF_STATUS;
+               trace_ent.health = bound(1, trace_ent.health + 100, trace_ent.max_health);
+               WaypointSprite_UpdateHealth(trace_ent.sprite, trace_ent.health);
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REPAIR);
+               
+               return TRUE;
+       }
+       if(cmd_name == "buffturret")
+       {
+               if((trace_ent.playerid != self.playerid || trace_ent.realowner != self) || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_UPGRADE);
+                       return TRUE;
+               }
+               if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)  
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_UPGRADE, autocvar_g_td_turret_upgrade_cost);
+                       return TRUE;
+               }
+               if(trace_ent.turret_buff >= 3)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXPOWER);
+                       return TRUE;
+               }
+               
+               self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
+               trace_ent.SendFlags |= TNSF_STATUS;
+               buffturret(trace_ent, 1.2);
+               WaypointSprite_UpdateHealth(trace_ent.sprite, trace_ent.health);
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_UPGRADE);
+               
+               return TRUE;
+       }
+       if(cmd_name == "turretremove")
+       {
+               if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (trace_ent.playerid == self.playerid || trace_ent.realowner == self))
+               {
+                       self.turret_cnt -= 1;
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REMOVE);
+                       WaypointSprite_Kill(trace_ent.sprite);
+                       remove(trace_ent.tur_head);
+                       remove(trace_ent);
+                       return TRUE;
+               }
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REMOVE);
+               return TRUE;
+       }
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_ClientConnect)
+{
+       entity t;
+       
+       self.turret_cnt = 0;
+       
+       for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); )
+       if(t.playerid == self.playerid)
+       {
+               t.realowner = self;
+               self.turret_cnt += 1;
+       }
+
+       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(TurretDies, td_TurretDies, 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, 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_HOOK(ClientConnect, td_ClientConnect, 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_Initialize();
+       }
+       
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back td_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               error("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return FALSE;
+}
diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qh b/qcsrc/server/mutators/gamemode_towerdefense.qh
new file mode 100644 (file)
index 0000000..b199fd5
--- /dev/null
@@ -0,0 +1,62 @@
+// 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;
+float m_speed_run;
+float m_speed_walk;
+
+// Turret defs
+.float turret_buff;
+
+// TD defs
+.float stat_current_wave;
+.float stat_totalwaves;
+.float spawntype;
+float spawn_delay;
+float max_current;
+float ignore_turrets;
+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;
+.float mspeed_run;
+.float mspeed_walk;
+.float spawndelay;
+.float maxcurrent;
+.float ignoreturrets;
+
+// Generator
+float gendestroyed;
+#define GENERATOR_MIN '-52 -52 -14'
+#define GENERATOR_MAX '52 52 75'
\ No newline at end of file
diff --git a/qcsrc/server/mutators/mutator_zombie_apocalypse.qc b/qcsrc/server/mutators/mutator_zombie_apocalypse.qc
new file mode 100644 (file)
index 0000000..3f84465
--- /dev/null
@@ -0,0 +1,148 @@
+// Zombie Apocalypse mutator - small side project
+// Spawns a defined number of zombies at the start of a match
+
+float za_numspawns, totalzombies, roundcnt, numzoms;
+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);
+               mon.spawnflags |= MONSTERFLAG_NORESPAWN;
+
+               if(trace_startsolid)
+               {
+                       sp = PickZombieSpawn();
+                       if(sp)
+                               setorigin(mon, sp.origin);
+               }
+                       
+        za_numspawns += 1;
+       }
+       else
+               zombie_spawn_somewhere();
+}
+
+void() spawn_zombies;
+void za_roundwon()
+{
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ZA_WIN);
+       
+       roundcnt += 1;
+       
+       numzoms = autocvar_g_za_monster_count;
+       
+       monsters_total = numzoms;
+       totalzombies = numzoms;
+       
+       self.think = spawn_zombies;
+       self.nextthink = time + 10;
+}
+
+void spawn_zombies ()
+{
+       self.nextthink = time + 1;
+       
+       if(totalzombies < 1)
+       {
+               self.think = za_roundwon;
+               self.nextthink = time;
+               return;
+       }
+       
+       if(gameover || numzoms <= 0)
+               return;
+               
+    entity e;
+    
+    dprint("Them zombies be spawnin'!\n");
+
+       while(numzoms > 0)
+       {
+        e = spawn();
+               e.think = zombie_spawn_somewhere;
+        e.nextthink = time;
+
+               numzoms -= 1;
+       }
+}
+
+void za_init ()
+{
+    entity e;
+       
+       roundcnt = 1;
+       
+    numzoms = autocvar_g_za_monster_count * roundcnt;
+       
+       monsters_total = numzoms;
+       totalzombies = numzoms;
+       
+    e = spawn();
+       e.think = spawn_zombies;
+       e.nextthink = time + 3;
+}
+
+MUTATOR_HOOKFUNCTION(za_ZombieDies)
+{
+       if(frag_attacker.classname == "player")
+               PlayerScore_Add(frag_attacker, SP_SCORE, 1);
+       
+       totalzombies -= 1;
+       monsters_killed += 1;
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(za_BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":Zombies");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(za_BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Zombies");
+       return 0;
+}
+
+MUTATOR_DEFINITION(mutator_zombie_apocalypse)
+{
+       MUTATOR_HOOK(MonsterDies, za_ZombieDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BuildMutatorsString, za_BuildMutatorsString, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BuildMutatorsPrettyString, za_BuildMutatorsPrettyString, CBC_ORDER_ANY);
+    
+    MUTATOR_ONADD
+    {
+        za_init();
+    }
+
+       return 0;
+}
index 4bdcbb28234834629ecc13f8f52767fcd1e4ce90..7a8b5764a5394fc404a56d0cb66dca5e84f32785 100644 (file)
@@ -4,7 +4,9 @@ MUTATOR_DECLARATION(gamemode_keepaway);
 MUTATOR_DECLARATION(gamemode_ctf);
 MUTATOR_DECLARATION(gamemode_nexball);
 MUTATOR_DECLARATION(gamemode_onslaught);
+MUTATOR_DECLARATION(gamemode_td);
 MUTATOR_DECLARATION(gamemode_domination);
+MUTATOR_DECLARATION(gamemode_rts);
 
 MUTATOR_DECLARATION(mutator_dodging);
 MUTATOR_DECLARATION(mutator_invincibleprojectiles);
@@ -15,5 +17,6 @@ MUTATOR_DECLARATION(mutator_spawn_near_teammate);
 MUTATOR_DECLARATION(mutator_physical_items);
 MUTATOR_DECLARATION(mutator_vampire);
 MUTATOR_DECLARATION(mutator_superspec);
+MUTATOR_DECLARATION(mutator_zombie_apocalypse);
 
 MUTATOR_DECLARATION(sandbox);
index f33be85b9b4c3816451ef7de35efe6492e91800a..166f3a823a45fd14b8f283f4364c3f19df8faf51 100644 (file)
@@ -41,6 +41,8 @@ mutators/gamemode_keyhunt.qh // TODO fix this
 mutators/gamemode_keepaway.qh
 mutators/gamemode_nexball.qh 
 mutators/mutator_dodging.qh
+mutators/gamemode_towerdefense.qh
+mutators/gamemode_rts.qh
 
 //// tZork Turrets ////
 tturrets/include/turrets_early.qh
@@ -213,6 +215,8 @@ playerstats.qc
 
 ../common/explosion_equation.qc
 
+monsters/monsters.qh
+
 mutators/base.qc
 mutators/gamemode_ctf.qc
 mutators/gamemode_domination.qc
@@ -221,6 +225,8 @@ mutators/gamemode_keyhunt.qc
 mutators/gamemode_keepaway.qc
 mutators/gamemode_nexball.qc
 mutators/gamemode_onslaught.qc
+mutators/gamemode_towerdefense.qc
+mutators/gamemode_rts.qc
 mutators/mutator_invincibleproj.qc
 mutators/mutator_new_toys.qc
 mutators/mutator_nix.qc
@@ -231,6 +237,7 @@ mutators/mutator_spawn_near_teammate.qc
 mutators/mutator_physical_items.qc
 mutators/sandbox.qc
 mutators/mutator_superspec.qc
+mutators/mutator_zombie_apocalypse.qc
 
 ../warpzonelib/anglestransform.qc
 ../warpzonelib/mathlib.qc
index d80b777a6f129197428f16146e36af1b32fc186a..3a879952c9e86e8af73050b9f2f52bb1401877d8 100644 (file)
@@ -10,6 +10,7 @@ void CreatureFrame (void)
                
                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)
                {
@@ -19,7 +20,7 @@ void CreatureFrame (void)
                                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)
                                {
index 6608fc9a317e38e1e73686179016a354ecdabe45..1fb7ad122d3adc8214190ab4667205b4a3e7ed52 100644 (file)
@@ -105,6 +105,22 @@ void InitGameplayMode()
                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_rts)
+       {
+               ActivateTeamplay();
+               fraglimit_override = 0;
+               leadlimit_override = 0;
+               MUTATOR_ADD(gamemode_rts);
+               have_team_spawns = -1; // request team spawns
+       }
 
        if(g_lms)
        {
index 971d65cca3916eea9f99a17d28b5d41ee096211f..19974401f325f2407c148d6df02ccaaa45ec221c 100644 (file)
@@ -22,6 +22,8 @@ void turret_stdproc_die()
     self.takedamage             = DAMAGE_NO;
 
     self.health             = 0;
+       
+       MUTATOR_CALLHOOK(TurretDies);
 
 // Go boom
     //RadiusDamage (self,self, min(self.ammo,50),min(self.ammo,50) * 0.25,250,world,min(self.ammo,50)*5,DEATH_TURRET,world);
@@ -64,6 +66,7 @@ void turret_stdproc_respawn()
     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;
index dd58b3cee2860e0c031dc131a47eab165c846967..ba42215aeb213567ba53047110125a7f24ef23cb 100644 (file)
@@ -571,6 +571,14 @@ float turret_stdproc_firecheck()
 float turret_validate_target(entity e_turret, entity e_target, float validate_flags)
 {
     vector v_tmp;
+       
+       turret_target = e_target;
+       turret_flags = validate_flags;
+       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;
@@ -1107,6 +1115,9 @@ float turret_stdproc_init (string cvar_base_name, string base, string head, floa
     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;
@@ -1339,7 +1350,6 @@ float turret_stdproc_init (string cvar_base_name, string base, string head, floa
         self.turret_score_target    = turret_stdproc_targetscore_generic;
 
     self.use = turret_stdproc_use;
-    self.bot_attack = TRUE;
 
     ++turret_count;
     self.nextthink = time + 1;
index c542dab401ba2ee88da3f88df7d7c57f48329478..16ecbce5e72167a4dd42e0c311e67d792d48fb06 100644 (file)
@@ -44,7 +44,10 @@ float turret_stdproc_targetscore_generic(entity _turret, entity _target)
     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);
index 8118b8f234dfa1cb23d2c52d737489679dd47ab1..f2e956586c45f42a36c68b7a4063a161a115bc12 100644 (file)
@@ -7,6 +7,18 @@ void turret_fusionreactor_fire()
     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);
+               self.enemy.SendFlags |= TNSF_STATUS;
+       }
+       if(g_rts) // monster healing
+       {
+               self.enemy.heal_delay = time + 0.5;
+               self.enemy.health = min(self.enemy.health + self.shot_dmg,self.enemy.max_health);
+               WaypointSprite_UpdateHealth(self.enemy.sprite, self.enemy.health);
+       }
     fl_org = 0.5 * (self.enemy.absmin + self.enemy.absmax);
     te_smallflash(fl_org);
 }
@@ -38,15 +50,43 @@ float turret_fusionreactor_firecheck()
                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(g_rts)
+       {
+               if(IsDifferentTeam(self, self.enemy))
+                       return 0;
+                       
+               if not(self.enemy.flags & FL_MONSTER)
+                       return 0;
+               
+               if(self.enemy.health >= self.enemy.max_health)
+                       return 0;
+                       
+               if(time < self.enemy.heal_delay)
+                       return 0;
+                       
+               return 1; // this is for monsters, so don't do the other checks
+       }
+       
        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)
index 20100de047203368a5d11e5a520a8cd8cdb39e11..b976bcd89543e94d1baf631788942abe06101a14 100644 (file)
@@ -107,7 +107,7 @@ void walker_rocket_think()
     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)
index e23cc702cc706cdb912cf0e6602d0c5ce351aa82..dd1ed6523c96a51c1bb793062837872577f6c987 100644 (file)
@@ -506,7 +506,7 @@ float vehicles_crushable(entity e)
     if(e.classname == "player")
         return TRUE;
 
-    if(e.classname == "monster_zombie")
+    if(e.flags & FL_MONSTER)
         return TRUE;
 
     return FALSE;
@@ -1315,6 +1315,9 @@ float vehicle_initialize(string  net_name,
     self.pos1 = self.origin;
     self.pos2 = self.angles;
     self.tur_head.team = self.team;
+       
+       if(MUTATOR_CALLHOOK(VehicleSpawn))
+               return FALSE;
 
     return TRUE;
 }
index 20aac867e91ae0f3b856be81fecc325b96f14180..cd32dae4f8b4340f100b9ada7254d33c2db41cc1 100644 (file)
@@ -253,7 +253,7 @@ void lgbeam_think()
                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;
diff --git a/scripts/barricade.shader b/scripts/barricade.shader
new file mode 100644 (file)
index 0000000..c650b5b
--- /dev/null
@@ -0,0 +1,8 @@
+barricade
+{
+ cull none
+ {
+       map textures/barricade.tga
+ }
+}
+
diff --git a/scripts/mage.shader b/scripts/mage.shader
new file mode 100644 (file)
index 0000000..7a235c1
--- /dev/null
@@ -0,0 +1,8 @@
+mage
+{
+       cull none
+
+       {
+               map textures/mage
+       }
+}
index a73e7e2064a38d6966bb223a7b425037d24b8901..8ed9769d00bf8942f3f4aaf6cfbd73a5dafd2e83 100644 (file)
@@ -14,3 +14,5 @@ tree
 tuba
 turrets
 weapons
+mage
+barricade
diff --git a/sound/monsters/zombie_death.ogg b/sound/monsters/zombie_death.ogg
new file mode 100644 (file)
index 0000000..7af422e
Binary files /dev/null and b/sound/monsters/zombie_death.ogg differ
diff --git a/sound/monsters/zombie_idle.ogg b/sound/monsters/zombie_idle.ogg
new file mode 100644 (file)
index 0000000..3dac288
Binary files /dev/null and b/sound/monsters/zombie_idle.ogg differ
diff --git a/sound/monsters/zombie_sight.ogg b/sound/monsters/zombie_sight.ogg
new file mode 100644 (file)
index 0000000..c033a9e
Binary files /dev/null and b/sound/monsters/zombie_sight.ogg differ
diff --git a/textures/barricade.tga b/textures/barricade.tga
new file mode 100644 (file)
index 0000000..d9cd774
Binary files /dev/null and b/textures/barricade.tga differ
diff --git a/textures/barricade_norm.tga b/textures/barricade_norm.tga
new file mode 100644 (file)
index 0000000..e664ed4
Binary files /dev/null and b/textures/barricade_norm.tga differ
diff --git a/textures/bloodyskull_pants.jpg b/textures/bloodyskull_pants.jpg
new file mode 100644 (file)
index 0000000..e0083ac
Binary files /dev/null and b/textures/bloodyskull_pants.jpg differ
diff --git a/textures/cerberus/cerberus_text.png b/textures/cerberus/cerberus_text.png
new file mode 100644 (file)
index 0000000..3ac7c05
Binary files /dev/null and b/textures/cerberus/cerberus_text.png differ
diff --git a/textures/cerberus/cerberus_text_pants.png b/textures/cerberus/cerberus_text_pants.png
new file mode 100644 (file)
index 0000000..d109711
Binary files /dev/null and b/textures/cerberus/cerberus_text_pants.png differ
diff --git a/textures/mage.tga b/textures/mage.tga
new file mode 100644 (file)
index 0000000..89c02fc
Binary files /dev/null and b/textures/mage.tga differ
diff --git a/textures/marine.tga b/textures/marine.tga
new file mode 100644 (file)
index 0000000..752c40f
Binary files /dev/null and b/textures/marine.tga differ
diff --git a/textures/marine_gloss.tga b/textures/marine_gloss.tga
new file mode 100644 (file)
index 0000000..deb4151
Binary files /dev/null and b/textures/marine_gloss.tga differ
diff --git a/textures/marine_glow.tga b/textures/marine_glow.tga
new file mode 100644 (file)
index 0000000..9af5a19
Binary files /dev/null and b/textures/marine_glow.tga differ
diff --git a/textures/marine_norm.tga b/textures/marine_norm.tga
new file mode 100644 (file)
index 0000000..850ce49
Binary files /dev/null and b/textures/marine_norm.tga differ
diff --git a/textures/marine_pants.tga b/textures/marine_pants.tga
new file mode 100644 (file)
index 0000000..1f295b8
Binary files /dev/null and b/textures/marine_pants.tga differ
diff --git a/textures/marine_shirt.tga b/textures/marine_shirt.tga
new file mode 100644 (file)
index 0000000..a31f3d0
Binary files /dev/null and b/textures/marine_shirt.tga differ
diff --git a/textures/meat_pants.tga b/textures/meat_pants.tga
new file mode 100644 (file)
index 0000000..1568b09
Binary files /dev/null and b/textures/meat_pants.tga differ
diff --git a/textures/spider/spidertex.tga b/textures/spider/spidertex.tga
new file mode 100644 (file)
index 0000000..e9f8df1
Binary files /dev/null and b/textures/spider/spidertex.tga differ
diff --git a/za.cfg b/za.cfg
new file mode 100644 (file)
index 0000000..1e6682e
--- /dev/null
+++ b/za.cfg
@@ -0,0 +1,2 @@
+set g_za 0 "Enable zombie apocalypse mutator"
+set g_za_monster_count 20
\ No newline at end of file