]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Sun, 4 Aug 2013 09:34:23 +0000 (19:34 +1000)
committerMario <mario.mario@y7mail.com>
Sun, 4 Aug 2013 09:34:23 +0000 (19:34 +1000)
143 files changed:
commands.cfg
defaultXonotic.cfg
effectinfo.txt
gamemodes.cfg
models/monsters/demon.mdl [new file with mode: 0644]
models/monsters/demon.mdl.framegroups [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/fish.mdl [new file with mode: 0644]
models/monsters/fish.mdl.framegroups [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/knight.mdl [new file with mode: 0644]
models/monsters/knight.mdl.framegroups [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/ogre.dpm [new file with mode: 0644]
models/monsters/ogre.dpm.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/slime.dpm [new file with mode: 0644]
models/monsters/slime.dpm.framegroups [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/wizard.mdl [new file with mode: 0644]
models/monsters/wizard.mdl.framegroups [new file with mode: 0644]
models/monsters/zombie.dpm.framegroups
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/monsters/zombie.dpm_3.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/Main.qc
qcsrc/client/View.qc
qcsrc/client/monsters.qc [new file with mode: 0644]
qcsrc/client/monsters.qh [new file with mode: 0644]
qcsrc/client/progs.src
qcsrc/client/projectile.qc
qcsrc/client/scoreboard.qc
qcsrc/client/waypointsprites.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/menu/xonotic/util.qc
qcsrc/server/accuracy.qc
qcsrc/server/autocvars.qh
qcsrc/server/bot/aim.qc
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_world.qc
qcsrc/server/generator.qc [new file with mode: 0644]
qcsrc/server/generator.qh [new file with mode: 0644]
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/monsters_early.qh [new file with mode: 0644]
qcsrc/server/monsters/lib/spawn.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/animus.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/bruiser.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/brute.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/cerberus.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/knight.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/mage.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/shambler.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/slime.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/spider.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/stingray.qc [new file with mode: 0644]
qcsrc/server/monsters/monster/wyvern.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_ctf.qc
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/mutators/gamemode_onslaught.qc
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_minstagib.qc
qcsrc/server/mutators/mutators.qh
qcsrc/server/progs.src
qcsrc/server/sv_main.qc
qcsrc/server/t_items.qc
qcsrc/server/target_spawn.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/monsters.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.jpg
textures/bloodyskull_pants.jpg [new file with mode: 0644]
textures/bloodyskull_robot_pants.tga [new file with mode: 0644]
textures/cerberus.png [new file with mode: 0644]
textures/cerberus_pants.png [new file with mode: 0644]
textures/mage.tga [new file with mode: 0644]
textures/mage_glow.tga [new file with mode: 0644]
textures/mage_pants.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.tga
textures/meat_alien.tga
textures/meat_alien_pants.tga [new file with mode: 0644]
textures/meat_pants.tga [new file with mode: 0644]
textures/meat_robot.tga
textures/meat_robot_pants.tga [new file with mode: 0644]
textures/ogre.png [new file with mode: 0644]
textures/ogre_pants.png [new file with mode: 0644]
textures/slime.png [new file with mode: 0644]
textures/slime_norm.png [new file with mode: 0644]
textures/slime_pants.png [new file with mode: 0644]
textures/spidertex.tga [new file with mode: 0644]
textures/spidertex_glow.tga [new file with mode: 0644]
textures/spidertex_pants.tga [new file with mode: 0644]

index 20f8b6bdabc4d77c9f5b728139f07baebf43632f..6a440ae22257e1fa2c2ff8b1419b2ad0636fb69f 100644 (file)
@@ -104,6 +104,8 @@ 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"
+alias menu_showmonstertools "menu_cmd directmenu MonsterTools"
 
 // command executed before loading a map by the menu
 // makes sure maxplayers is at least minplayers or bot_number + 1
@@ -156,6 +158,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
@@ -174,6 +179,10 @@ alias spec "spectate"
 
 // mutator aliases
 alias sandbox "cmd g_sandbox ${* ?}"
+alias spawnturret "cmd turretspawn ${* ?}"
+alias upgradeturret "cmd buffturret ${* ?}"
+alias rmturret "cmd turretremove ${* ?}"
+alias repairturret "cmd repairturret ${* ?}"
 
 
 // ============================================================
@@ -185,6 +194,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 c392e9a1580728f922da3abfadfce58a2e25d1f3..c5275cc9521257cd688ff3ea3653745df2d661b3 100644 (file)
@@ -448,6 +448,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"
 
@@ -1572,6 +1580,7 @@ exec vehicles.cfg
 exec crosshairs.cfg
 exec gamemodes.cfg
 exec notifications.cfg
+exec monsters.cfg
 
 // load console command aliases and settings
 exec commands.cfg
index c2f702fa5ab243ac7d032a8583bb07e0c8d82570..05e881c5da6b5e9d9adc122128fe586d0d0dc681 100644 (file)
@@ -7977,3 +7977,43 @@ size 100 100
 alpha 190 190 180
 sizeincrease -80
 color 0xFFFFFF 0xFFFFFF
+
+// waypoint_link_red - red waypoint linking effect
+effect waypoint_link_red
+countabsolute 1
+type beam
+tex 200 200
+size 1 1
+alpha 256 256 64
+color 0xFF0F0F 0xFF0F0F
+sizeincrease 1
+
+// waypoint_link_blue - blue waypoint linking effect
+effect waypoint_link_blue
+countabsolute 1
+type beam
+tex 200 200
+size 1 1
+alpha 256 256 64
+color 0x0F0FFF 0x0F0FFF
+sizeincrease 1
+
+// waypoint_link_yellow - yellow waypoint linking effect
+effect waypoint_link_yellow
+countabsolute 1
+type beam
+tex 200 200
+size 1 1
+alpha 256 256 64
+color 0xFFFF0F 0xFFFF0F
+sizeincrease 1
+
+// waypoint_link_pink - pink waypoint linking effect
+effect waypoint_link_pink
+countabsolute 1
+type beam
+tex 200 200
+size 1 1
+alpha 256 256 64
+color 0xFF0FFF 0xFF0FFF
+sizeincrease 1
index a1ad723d2748dd155db645f1f44ec0a633cd9bdb..2177129195d55faa80a09bcd26d07728c7605bfa 100644 (file)
@@ -37,6 +37,7 @@ 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_gameend
 alias cl_hook_activeweapon
 
@@ -58,6 +59,7 @@ 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_gamerestart
 alias sv_hook_gameend
 
@@ -134,6 +136,9 @@ 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
 
 
 // =======
@@ -218,6 +223,7 @@ set g_ctf_pass_timelimit 2 "how long a flag can stay trying to pass before it gi
 set g_ctf_pass_velocity 750 "how fast or far a player can pass the flag"
 set g_ctf_allow_vehicle_touch 0 "allow flags to be picked up/captured/returned without even leaving the vehicle"
 set g_ctf_allow_vehicle_carry 1 "allow players to hold flags inside a vehicle"
+set g_ctf_allow_monster_touch 0 "allow flags to be returned by monsters"
 
 set g_ctf_shield_max_ratio 0   "shield at most this percentage of a team from the enemy flag (try: 0.4 for 40%)"
 set g_ctf_shield_min_negscore 20       "shield the player from the flag if he's got this negative amount of points or less"
@@ -419,3 +425,39 @@ 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_majority_factor 0.8
+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 150
+set g_td_monsters_speed_run 170
+set g_td_monsters_spawn_delay 1.5
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/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/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/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..c495edd
--- /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 1 // hellknight charge1\r80 13 10 1 // hellknight magic1\r94 12 10 1 // hellknight magic2\r107 5 10 1 // hellknight charge2\r113 9 10 1 // hellknight slice\r123 9 10 1 // hellknight smash\r133 21 10 1 // hellknight weapon attack\r155 10 10 1 //hellknight magic3
\ 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/mage.dpm b/models/monsters/mage.dpm
new file mode 100644 (file)
index 0000000..2e50fb5
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/ogre.dpm b/models/monsters/ogre.dpm
new file mode 100644 (file)
index 0000000..c38482d
Binary files /dev/null and b/models/monsters/ogre.dpm differ
diff --git a/models/monsters/ogre.dpm.framegroups b/models/monsters/ogre.dpm.framegroups
new file mode 100644 (file)
index 0000000..03dd229
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+Generated framegroups file for ogre
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 31 60 1 // ogre idle
+32 31 60 1 // ogre walk
+63 31 120 1 // ogre walk
+94 31 60 1 // ogre pain
+125 101 60 1 // ogre swing
+226 76 60 0 // ogre die
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/slime.dpm b/models/monsters/slime.dpm
new file mode 100644 (file)
index 0000000..095d5f7
Binary files /dev/null and b/models/monsters/slime.dpm differ
diff --git a/models/monsters/slime.dpm.framegroups b/models/monsters/slime.dpm.framegroups
new file mode 100644 (file)
index 0000000..4c1cf73
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+Generated framegroups file for slime
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 36 30 1 // slime idle
+37 71 30 1 // slime walk
+108 51 30 1 // slime attack
+159 2 30 1 // slime fly
+161 61 30 1 // slime pain
+222 61 30 0 // slime die
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/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
index 3d56cdb2f452d770d5896be4e177e9c5355c80f1..3d38a2864cc49b0f3a9f34be0bc73c6da21ef617 100644 (file)
@@ -1,4 +1,4 @@
-1 56 30 0      // zombie attackleap         1
+1 56 30 1      // zombie attackleap         1
 57 41 60 1     // zombie attackrun1         2
 98 41 60 1     // zombie attackrun2         3
 139 41 60 1    // zombie attackrun3         4
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/monsters/zombie.dpm_3.skin b/models/monsters/zombie.dpm_3.skin
new file mode 100644 (file)
index 0000000..ad7e545
--- /dev/null
@@ -0,0 +1,2 @@
+bloodyskull,cleanskull
+meat,meat
\ 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..6589064
--- /dev/null
@@ -0,0 +1,209 @@
+// Misc
+set g_monsters 1 "Enable monsters (master switch)"
+set g_monsters_think_delay 0.1 "Delay between monster think frames"
+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_time 10 "Amount of time before monster's dropped loot disappears"
+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 in team matches if set to 1"
+set g_monsters_teams 1
+set g_monsters_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_target_range 2000
+set g_monsters_target_infront 0 "Only find targets infront of the monster"
+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 0 "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)"
+
+// Brute
+set g_monster_brute 1 "Enable Brutes"
+set g_monster_brute_health 200 "Brute health"
+set g_monster_brute_chainsaw_damage 15 "Brute chainsaw damage (hits multiple times)"
+set g_monster_brute_drop ammo "Brute drops this item on death"
+set g_monster_brute_drop_size bullets "Size of the item Brutes drop. Possible values are: small, medium, large"
+set g_monster_brute_speed_walk 100 "Brute walk speed"
+set g_monster_brute_speed_run 150 "Brute run speed"
+set g_monster_brute_attack_uzi_bullets 3 "Number of machine gun bullets Brute fires"
+set g_monster_brute_attack_uzi_chance 0.1 "Chance for Brute to fire machine gun"
+set g_monster_brute_attack_uzi_damage 10 "Brute machine gun damage per bullet"
+set g_monster_brute_attack_uzi_force 5 "Brute machine gun knockback"
+set g_monster_brute_attack_grenade_damage 50 "Brute grenade damage"
+set g_monster_brute_attack_grenade_edgedamage 25 "Brute grenade indirect hit damage"
+set g_monster_brute_attack_grenade_radius 200 "Brute grenade explosion radius"
+set g_monster_brute_attack_grenade_force 15 "Brute grenade knockback"
+
+// Animus
+set g_monster_animus 1 "Enable Animuses"
+set g_monster_animus_health 300 "Animus health"
+set g_monster_animus_attack_jump_damage 40 "Animus jump attack damage"
+set g_monster_animus_damage 20 "Animus melee attack damage"
+set g_monster_animus_drop health "Animus drops this item on death"
+set g_monster_animus_drop_size medium "Size of the item Animuses drop. Possible values are: small, medium, large"
+set g_monster_animus_speed_walk 150 "Animus walk speed"
+set g_monster_animus_speed_run 300 "Animus 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"
+
+// Bruiser
+set g_monster_bruiser 1 "Enable Bruisers"
+set g_monster_bruiser_health 75 "Bruiser Health"
+set g_monster_bruiser_drop armor "Bruiser drops this item on death"
+set g_monster_bruiser_drop_size medium "Size of the item Bruisers drop. Possible values are: small, medium, large"
+set g_monster_bruiser_melee_damage 20 "Bruiser melee attack damage"
+set g_monster_bruiser_melee_side_damage 10 "Bruiser melee attack side damage"
+set g_monster_bruiser_speed_walk 40 "Bruiser walk speed"
+set g_monster_bruiser_speed_run 70 "Bruiser run speed"
+
+// Wyvern
+set g_monster_wyvern 1 "Enable Wyverns"
+set g_monster_wyvern_health 80 "Wyvern health"
+set g_monster_wyvern_drop ammo "Wyvern drops this item on death"
+set g_monster_wyvern_drop_size cells "Size of the item Wyverns drop. Possible values are: small, medium, large"
+set g_monster_wyvern_speed_walk 40 "Wyvern walk speed"
+set g_monster_wyvern_speed_run 70 "Wyvern run speed"
+set g_monster_wyvern_fireball_damagetime 3 "How long the enemy will burn if it's within fireball radius"
+set g_monster_wyvern_fireball_damage 30 "Wyvern fireball projectile damage"
+set g_monster_wyvern_fireball_edgedamage 20 "Wyvern fireball indirect hit damage"
+set g_monster_wyvern_fireball_force 50 "Wyvern fireball projectile push force"
+set g_monster_wyvern_fireball_radius 70 "Wyvern fireball projectile damage radius"
+set g_monster_wyvern_fireball_speed 400 "Wyvern fireball projectile speed"
+
+// Cerberus
+set g_monster_cerberus 1 "Enable Cerberuses"
+set g_monster_cerberus_health 25 "Cerberus health"
+set g_monster_cerberus_bite_damage 15 "Cerberus bite attack damage"
+set g_monster_cerberus_attack_jump_damage 30 "Cerberus jump attack damage"
+set g_monster_cerberus_drop health "Cerberus drops this item on death"
+set g_monster_cerberus_drop_size small "Size of the item Cerberuss drop. Possible values are: small, medium, large"
+set g_monster_cerberus_speed_walk 60 "Cerberus walk speed"
+set g_monster_cerberus_speed_run 120 "Cerberus run speed"
+
+// Slime
+set g_monster_slime 1 "Enable Slime"
+set g_monster_slime_health 80 "Slime health"
+set g_monster_slime_drop ammo "Slime drops this item when it explodes"
+set g_monster_slime_drop_size rockets "Size of the item Slime drops. Possible values are: small, medium, large"
+set g_monster_slime_speed_walk 20 "Slime walk speed"
+set g_monster_slime_speed_run 30 "Slime run speed"
+
+// Knight
+set g_monster_knight 1 "Enable Knights"
+set g_monster_knight_health 250 "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_inferno_damage 40 "Knight inferno damage"
+set g_monster_knight_inferno_chance 0.4 "Knight inferno attack chance"
+set g_monster_knight_inferno_damagetime 3 "How long the inferno should burn the player"
+set g_monster_knight_fireball_damage 30 "Knight fireball projectile damage"
+set g_monster_knight_fireball_edgedamage 10 "Knight fireball indirect hit damage"
+set g_monster_knight_fireball_force 50 "Knight fireball projectile push force"
+set g_monster_knight_fireball_radius 70 "Knight fireball projectile damage radius"
+set g_monster_knight_fireball_speed 600 "Knight fireball projectile speed"
+set g_monster_knight_fireball_spread 0 "Knight fireball projectile spread"
+set g_monster_knight_fireball_chance 0.3 "Chance for Knight to throw a fireball"
+set g_monster_knight_jump_chance 0.2 "Chance for Knight to jump at the player (always 1 if enemy is further than _dist)"
+set g_monster_knight_jump_damage 25 "Knight jump attack damage"
+set g_monster_knight_jump_dist 500 "Knight will prioritise jumping if the enemy is this far away"
+set g_monster_knight_melee_damage 20 "Knight melee attack damage"
+set g_monster_knight_spike_damage 5 "Knight spike projectile damage"
+set g_monster_knight_spike_edgedamage 5 "Knight spike projectile indirect hit damage"
+set g_monster_knight_spike_radius 20 "Knight spike projectile damage radius"
+set g_monster_knight_spike_force 5 "Knight spike projectile force"
+set g_monster_knight_spike_chance 0.5 "Knight spike attack chance"
+set g_monster_knight_speed_walk 75 "Knight walk speed"
+set g_monster_knight_speed_run 150 "Knight run speed"
+
+// Stingray
+set g_monster_stingray 1 "Enable Stingray"
+set g_monster_stingray_health 25 "Stingray health"
+set g_monster_stingray_damage 10 "Stingray bite attack damage"
+set g_monster_stingray_drop health "Stingray drops this item on death"
+set g_monster_stingray_drop_size small "Size of the item Stingray drop. Possible values are: small, medium, large"
+set g_monster_stingray_speed_walk 40 "Stingray walk speed"
+set g_monster_stingray_speed_run 70 "Stingray run speed"
+
+// Mage
+set g_monster_mage 1 "Enable Mages"
+set g_monster_mage_health 200 "Mage health"
+set g_monster_mage_drop health "Mage drops this item on death"
+set g_monster_mage_drop_size medium "Size of the item Mages drop. Possible values are: small, medium, large"
+set g_monster_mage_speed 50 "Mage move speed"
+set g_monster_mage_attack_spike_damage 30 "Mage homing spike explosion damage"
+set g_monster_mage_attack_spike_radius 60 "Mage homing spike explosion radius"
+set g_monster_mage_attack_spike_delay 2 "Delay between Mage homing spike attacks"
+set g_monster_mage_attack_melee_damage 30 "Mage magic attack damage"
+set g_monster_mage_attack_melee_delay 0.7 "Delay between Mage melee attacks"
+set g_monster_mage_heal_self 35 "Amount of health Mage will regenerate every attack when its low on health"
+set g_monster_mage_heal_friends 15 "Amount of health Mage will regenerate nearby friends"
+set g_monster_mage_heal_minhealth 250 "Health limit below which Mage will try to heal itself"
+set g_monster_mage_heal_range 200 "Maximum healing distance"
+set g_monster_mage_heal_delay 1.5 "Delay between healing bursts"
+set g_monster_mage_shield_blockpercent 40 "% of damage inflicted on Mage if using a force field"
+set g_monster_mage_shield_delay 7 "Refire for Mage force shield"
+set g_monster_mage_shield_time 3 "Amount of time Mage force field lasts"
+set g_monster_mage_attack_grenade_damage 25 "Mage fake item grenade attack damage"
+set g_monster_mage_attack_grenade_edgedamage 20 "Mage fake item grenade attack edge damage"
+set g_monster_mage_attack_grenade_radius 100 "Mage fake item grenade attack explosion radius"
+set g_monster_mage_attack_grenade_lifetime 5 "Mage fake item grenade life time"
+set g_monster_mage_attack_grenade_speed 150 "Mage fake item grenade forward speed"
+set g_monster_mage_attack_grenade_speed_up 95 "Mage fake item grenade upwards speed"
+set g_monster_mage_attack_grenade_speed_z 0 "Mage fake item grenade speed angle"
+set g_monster_mage_attack_grenade_spread 0 "Mage fake item grenade spread"
+set g_monster_mage_attack_grenade_force 170 "Mage fake item grenade damage knockback"
+set g_monster_mage_attack_grenade_chance 30 "% chance of Mage attack being fake item grenade"
+
+// 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_attack_fire_time 2 "Spider fire attack burn time"
+set g_monster_spider_attack_web_speed 1000 "Spider web fly speed"
+set g_monster_spider_attack_web_speed_up 150 "Spider web upwards fly speed"
+set g_monster_spider_attack_web_spread 0 "Spider web spread"
+set g_monster_spider_attack_web_speed_z 0 "Spider web upwards angle"
+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"
index 42fadc0c18a86b0cc0cec1eb145409adebe08efb..6eef5cc0df53ddaa54e822b2332a179da52a9302 100644 (file)
@@ -116,6 +116,8 @@ void CSQC_Init(void)
        precache_sound("misc/hit.wav");
        precache_sound("misc/typehit.wav");
 
+       Monsters_Precache();
+       generator_precache();
        Projectile_Precache();
        Hook_Precache();
        GibSplash_Precache();
@@ -823,6 +825,8 @@ void CSQC_Ent_Update(float bIsNewEntity)
                case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break;
                case ENT_CLIENT_AUXILIARYXHAIR: Net_AuXair2(bIsNewEntity); break;
                case ENT_CLIENT_TURRET: ent_turret(); break; 
+               case ENT_CLIENT_MONSTER: ent_monster(); break;
+               case ENT_CLIENT_GENERATOR: ent_generator(); break;
                case ENT_CLIENT_MODEL: CSQCModel_Read(bIsNewEntity); break;
                case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break;  
                case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break;
index 13e41a4167c531dcbba9387dfca2af50cb29ab2b..663701d106e75e31589467dfb86d0794171b5123 100644 (file)
@@ -987,7 +987,7 @@ void CSQC_UpdateView(float w, float h)
                        }
                }
        }
-
+       
        float e1 = (autocvar_hud_postprocessing_maxbluralpha != 0);
        float e2 = (autocvar_hud_powerup != 0);
        if(autocvar_hud_postprocessing && (e1 || e2)) // TODO: Remove this code and re-do the postprocess handling in the engine, where it properly belongs.
@@ -1079,15 +1079,12 @@ void CSQC_UpdateView(float w, float h)
 
        //else
        {
-               if(gametype == MAPINFO_TYPE_FREEZETAG)
+               if(getstati(STAT_FROZEN))
+                       drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+               if(getstatf(STAT_REVIVE_PROGRESS))
                {
-                       if(getstati(STAT_FROZEN))
-                               drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
-                       if(getstatf(STAT_REVIVE_PROGRESS))
-                       {
-                               DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
-                               drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
-                       }
+                       DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+                       drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
                }
 
                if(autocvar_r_letterbox == 0)
diff --git a/qcsrc/client/monsters.qc b/qcsrc/client/monsters.qc
new file mode 100644 (file)
index 0000000..c3eef3c
--- /dev/null
@@ -0,0 +1,339 @@
+string mid2info_model;
+string mid2info_name;
+vector mid2info_min;
+vector mid2info_max;
+
+float monster_precached[MONSTER_LAST];
+void monster_mid2info(float _mid);
+
+void monster_precache(float _mid)
+{    
+    monster_mid2info(_mid);
+    if(monster_precached[_mid])
+        return;
+
+    switch(_mid)
+    {
+        case MONSTER_ZOMBIE:
+               {
+                       precache_model(ZOMBIE_MODEL);
+                       break;
+               }
+               case MONSTER_BRUTE:
+               {
+                       precache_model(BRUTE_MODEL);
+                       break;
+               }
+               case MONSTER_ANIMUS:
+               {
+                       precache_model(ANIMUS_MODEL);
+                       break;
+               }
+               case MONSTER_SHAMBLER:
+               {
+                       precache_model(SHAMBLER_MODEL);
+                       break;
+               }
+               case MONSTER_BRUISER:
+               {
+                       precache_model(BRUISER_MODEL);
+                       break;
+               }
+               case MONSTER_WYVERN:
+               {
+                       precache_model(WYVERN_MODEL);
+                       break;
+               }
+               case MONSTER_CERBERUS:
+               {
+                       precache_model(CERBERUS_MODEL);
+                       break;
+               }
+               case MONSTER_SLIME:
+               {
+                       precache_model(SLIME_MODEL);
+                       precache_sound("weapons/rocket_impact.wav");
+                       break;
+               }
+               case MONSTER_KNIGHT:
+               {
+                       precache_model(KNIGHT_MODEL);
+                       break;
+               }
+               case MONSTER_STINGRAY:
+               {
+                       precache_model(STINGRAY_MODEL);
+                       break;
+               }
+               case MONSTER_MAGE:
+               {
+                       precache_model(MAGE_MODEL);
+                       break;
+               }
+               case MONSTER_SPIDER:
+               {
+                       precache_model(SPIDER_MODEL);
+
+                       break;
+               }
+    }
+       
+    monster_precached[_mid] = TRUE;
+}
+
+void Monsters_Precache()
+{
+       float i;
+       for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
+               monster_precache(i);
+}
+
+void monster_mid2info(float _mid)
+{
+       switch(_mid)
+       {
+               case MONSTER_ZOMBIE:
+               {
+                       mid2info_model = ZOMBIE_MODEL;
+                       mid2info_name = "Zombie";
+                       mid2info_min = ZOMBIE_MIN;
+                       mid2info_max = ZOMBIE_MAX;
+                       break;
+               }
+               case MONSTER_BRUTE:
+               {
+                       mid2info_model = BRUTE_MODEL;
+                       mid2info_name = "Brute";
+                       mid2info_min = BRUTE_MIN;
+                       mid2info_max = BRUTE_MAX;
+                       break;
+               }
+               case MONSTER_ANIMUS:
+               {
+                       mid2info_model = ANIMUS_MODEL;
+                       mid2info_name = "Animus";
+                       mid2info_min = ANIMUS_MIN;
+                       mid2info_max = ANIMUS_MAX;
+                       if(self) self.scale = 1.3;
+                       break;
+               }
+               case MONSTER_SHAMBLER:
+               {
+                       mid2info_model = SHAMBLER_MODEL;
+                       mid2info_name = "Shambler";
+                       mid2info_min = SHAMBLER_MIN;
+                       mid2info_max = SHAMBLER_MAX;
+                       if(self) self.scale = 1.3;
+                       break;
+               }
+               case MONSTER_BRUISER:
+               {
+                       mid2info_model = BRUISER_MODEL;
+                       mid2info_name = "Bruiser";
+                       mid2info_min = BRUISER_MIN;
+                       mid2info_max = BRUISER_MAX;
+                       if(self) self.scale = 1.3;
+                       break;
+               }
+               case MONSTER_WYVERN:
+               {
+                       mid2info_model = WYVERN_MODEL;
+                       mid2info_name = "Wyvern";
+                       mid2info_min = WYVERN_MIN;
+                       mid2info_max = WYVERN_MAX;
+                       if(self) self.scale = 1.3;
+                       break;
+               }
+               case MONSTER_CERBERUS:
+               {
+                       mid2info_model = CERBERUS_MODEL;
+                       mid2info_name = "Cerberus";
+                       mid2info_min = CERBERUS_MIN;
+                       mid2info_max = CERBERUS_MAX;
+                       break;
+               }
+               case MONSTER_SLIME:
+               {
+                       mid2info_model = SLIME_MODEL;
+                       mid2info_name = "Slime";
+                       mid2info_min = SLIME_MIN;
+                       mid2info_max = SLIME_MAX;
+                       break;
+               }
+               case MONSTER_KNIGHT:
+               {
+                       mid2info_model = KNIGHT_MODEL;
+                       mid2info_name = "Knight";
+                       mid2info_min = KNIGHT_MIN;
+                       mid2info_max = KNIGHT_MAX;
+                       if(self) self.scale = 1.3;
+                       break;
+               }
+               case MONSTER_STINGRAY:
+               {
+                       mid2info_model = STINGRAY_MODEL;
+                       mid2info_name = "Stingray";
+                       mid2info_min = STINGRAY_MIN;
+                       mid2info_max = STINGRAY_MAX;
+                       if(self) self.scale = 1.3;
+                       break;
+               }
+               case MONSTER_MAGE:
+               {
+                       mid2info_model = MAGE_MODEL;
+                       mid2info_name = "Mage";
+                       mid2info_min = MAGE_MIN;
+                       mid2info_max = MAGE_MAX;
+                       break;
+               }
+               case MONSTER_SPIDER:
+               {
+                       mid2info_model = SPIDER_MODEL;
+                       mid2info_name = "Spider";
+                       mid2info_min = SPIDER_MIN;
+                       mid2info_max = SPIDER_MAX;
+                       break;
+               }
+               default:
+               {
+                       dprint("WARNING: Unknown monster in CSQC\n");
+                       break;
+               }
+       }       
+}
+
+.vector glowmod;
+void monster_changeteam()
+{
+       self.glowmod = Team_ColorRGB(self.team - 1);
+       self.teamradar_color = Team_ColorRGB(self.team - 1);
+       
+       if(self.team)
+               self.colormap = 1024 + (self.team - 1) * 17;
+       else
+               self.colormap = 1024;
+}
+
+void monster_die()
+{
+       if(self.monsterid == MONSTER_SPIDER)
+               self.angles += '180 0 0';
+               
+       self.solid = SOLID_CORPSE;
+}
+
+void monster_draw()
+{        
+       float dt;
+            
+       dt = time - self.move_time;
+       self.move_time = time;
+       if(dt <= 0)
+               return;
+    
+       fixedmakevectors(self.angles);
+       //movelib_groundalign4point(50, 25, 0.25, 45);
+       setorigin(self, self.origin + self.velocity * dt);
+       self.angles_y = self.move_angles_y;
+}
+
+void monster_construct()
+{      
+       monster_mid2info(self.monsterid);
+       self.netname = mid2info_name;
+
+       setorigin(self, self.origin);
+       setmodel(self, mid2info_model);
+       setsize(self, mid2info_min, mid2info_max);
+       
+       self.move_movetype      = MOVETYPE_BOUNCE;
+       self.health                     = 255;
+       self.solid                      = SOLID_BBOX;
+       self.movetype           = MOVETYPE_BOUNCE; 
+       self.move_origin        = self.origin;
+       self.move_time          = time;
+       self.drawmask           = MASK_NORMAL;  
+       self.alpha                      = 1;
+       self.draw                       = monster_draw;
+}
+
+void ent_monster()
+{
+       float sf;
+       sf = ReadByte();
+
+       if(sf & MSF_SETUP)
+       {
+               self.monsterid = ReadByte();
+                               
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
+               
+               self.angles_x = ReadAngle();
+               self.angles_y = ReadAngle();
+               
+               self.skin = ReadByte();
+               self.team = ReadByte();
+               
+               monster_precache(self.monsterid);
+               monster_construct();
+               monster_changeteam();
+       }
+       
+       if(sf & MSF_ANG)
+       {
+               self.move_angles_x = ReadShort();
+               self.move_angles_y = ReadShort();
+               self.angles = self.move_angles;
+       }
+       
+       if(sf & MSF_MOVE)
+       {
+               self.origin_x = ReadShort();
+               self.origin_y = ReadShort();
+               self.origin_z = ReadShort();
+               setorigin(self, self.origin);
+               
+               self.velocity_x = ReadShort();
+               self.velocity_y = ReadShort();
+               self.velocity_z = ReadShort();
+               
+               self.move_angles_y = ReadShort();
+                       
+               self.move_time   = time;
+               self.move_velocity = self.velocity;
+               self.move_origin   = self.origin;
+       }
+       
+       if(sf & MSF_ANIM)
+       {
+               self.frame1time = ReadCoord();
+               self.frame        = ReadByte();
+       }
+
+       if(sf & MSF_STATUS)
+       {
+               self.skin = ReadByte();
+       
+               float _tmp;
+               _tmp = ReadByte();
+               if(_tmp != self.team)
+               {                       
+                       self.team = _tmp;                               
+                       monster_changeteam();
+               }
+               
+               _tmp = ReadByte();
+               if(_tmp == 4) // respawning
+                       setmodel(self, "null");
+               
+               _tmp = ReadByte();
+               
+               if(_tmp == 0 && self.health != 0)
+                       monster_die();
+
+               self.health = _tmp;
+       }
+}
diff --git a/qcsrc/client/monsters.qh b/qcsrc/client/monsters.qh
new file mode 100644 (file)
index 0000000..83dac29
--- /dev/null
@@ -0,0 +1,2 @@
+void ent_monster();
+void Monsters_Precache();
index 114f0a5b5c77fdd666db5ae4953b653fa40302e5..fd700325050b11a314e9bde77a13b03461c65686 100644 (file)
@@ -47,7 +47,10 @@ bgmscript.qh
 noise.qh
 tturrets.qh
 ../server/tturrets/include/turrets_early.qh
+monsters.qh
+../server/monsters/lib/monsters_early.qh
 ../server/movelib.qc
+../server/generator.qh
 main.qh
 vehicles/vehicles.qh
 ../common/csqcmodel_settings.qh
@@ -119,6 +122,11 @@ command/cl_cmd.qc
 ../warpzonelib/client.qc
 tturrets.qc
 
+../server/generator.qc
+
+../server/monsters/monsters.qh
+monsters.qc
+
 player_skeleton.qc
 ../common/animdecide.qc
 
index 09276650dda5974782bec2eb0f322601c3c263dc..1283ff94575aa73ece5a41da878d561a6930b311 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_MAGE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_VORESPIKE"); 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 743e4b76d7bd9af97d949eab7a6027174b0c918b..104c44252ce34cf2cb1de40c2e50978b2ea4a5d9 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 2066aec68c7dd189bfcd8c30e48f27b8f94f4ce9..462dc8620c159bb940cba329c01c726c357bb9c3 100644 (file)
@@ -316,7 +316,7 @@ string spritelookuptext(string s)
                case "item-shield": return _("Shield");
                case "item-fuelregen": return _("Fuel regen");
                case "item-jetpack": return _("Jet Pack");
-               case "freezetag_frozen": return _("Frozen!");
+               case "frozen": return _("Frozen!");
                case "tagged-target": return _("Tagged");
                case "vehicle": return _("Vehicle");
                default: return s;
index b399f3857c3454e3c16572d0aed19a7aea627801..7b403ddd107609a9ec804e324e837776e38d3c9e 100644 (file)
@@ -101,6 +101,8 @@ const float ENT_CLIENT_NOTIFICATION = 38;
 const float ENT_CLIENT_TURRET = 40;
 const float ENT_CLIENT_AUXILIARYXHAIR = 50;
 const float ENT_CLIENT_VEHICLE = 60;
+const float ENT_CLIENT_MONSTER = 70;
+const float ENT_CLIENT_GENERATOR = 80;
 
 const float SPRITERULE_DEFAULT = 0;
 const float SPRITERULE_TEAMPLAY = 1;
@@ -180,6 +182,12 @@ const float STAT_SECRETS_FOUND = 71;
 const float STAT_RESPAWN_TIME = 72;
 const float STAT_ROUNDSTARTTIME = 73;
 
+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;
@@ -355,6 +363,8 @@ float PROJECTILE_WAKICANNON     = 29;
 float PROJECTILE_BUMBLE_GUN     = 30;
 float PROJECTILE_BUMBLE_BEAM    = 31;
 
+float PROJECTILE_MAGE_SPIKE            = 32;
+
 float SPECIES_HUMAN        =  0;
 float SPECIES_ROBOT_SOLID  =  1;
 float SPECIES_ALIEN        =  2;
index f9a9ceba25b1e0df2da7e77db433f5982e644b99..7ae83f802e3ffcf601fb77be1262a9f2e034ec02 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_CERBERUS_BITE,  DEATH_SELF_MON_CERBERUS_BITE,           DEATH_MURDER_MONSTER,              DEATH_MONSTER_FIRST) \
+       DEATHTYPE(DEATH_MONSTER_CERBERUS_JUMP,  DEATH_SELF_MON_CERBERUS_JUMP,           DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_ANIMUS,                 DEATH_SELF_MON_ANIMUS,                          DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_STINGRAY,               DEATH_SELF_MON_STINGRAY,                        DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_KNIGHT_CRUSH,   DEATH_SELF_MON_KNIGHT_CRUSH,            DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_KNIGHT_FBALL,   DEATH_SELF_MON_KNIGHT_FBALL,            DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_KNIGHT_INFERNO, DEATH_SELF_MON_KNIGHT_INFERNO,          DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_KNIGHT_MELEE,   DEATH_SELF_MON_KNIGHT_MELEE,            DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_KNIGHT_SPIKE,   DEATH_SELF_MON_KNIGHT_SPIKE,            DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_BRUISER,                DEATH_SELF_MON_BRUISER,                         DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_BRUTE_BLADE,    DEATH_SELF_MON_BRUTE_BLADE,                     DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_BRUTE_GRENADE,  DEATH_SELF_MON_BRUTE_GRENADE,           DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_BRUTE_UZI,              DEATH_SELF_MON_BRUTE_UZI,                       DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_MAGE,                   DEATH_SELF_MON_MAGE,                            DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SHAMBLER_CLAW,  DEATH_SELF_MON_SHAMBLER_CLAW,           DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SHAMBLER_SMASH, DEATH_SELF_MON_SHAMBLER_SMASH,          DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SHAMBLER_ZAP,   DEATH_SELF_MON_SHAMBLER_ZAP,            DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SPIDER,                 DEATH_SELF_MON_SPIDER,                          DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SPIDER_FIRE,    DEATH_SELF_MON_SPIDER_FIRE,                     DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_SLIME,                  DEATH_SELF_MON_SLIME,                           DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_WYVERN,                 DEATH_SELF_MON_WYVERN,                          DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_ZOMBIE_JUMP,    DEATH_SELF_MON_ZOMBIE_JUMP,                     DEATH_MURDER_MONSTER,              NORMAL_POS) \
+       DEATHTYPE(DEATH_MONSTER_ZOMBIE_MELEE,   DEATH_SELF_MON_ZOMBIE_MELEE,            DEATH_MURDER_MONSTER,              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 +110,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 7570393f4871669d6a649b396e15777e5b5b7f2d..2c611b2f58067ecf494b3d76f991269076ea2601 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" || v == "td_spawnpoint")
+                                       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..fdfc5eabd2bcfb1992a68a0c8cb32a33e7fd57d7 100644 (file)
@@ -39,6 +39,9 @@ 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=1 leadlimit=0")
+#define g_td IS_GAMETYPE(TD)
+
 REGISTER_GAMETYPE(_("Arena"),arena,g_arena,ARENA,"timelimit=20 pointlimit=10 leadlimit=0")
 #define g_arena IS_GAMETYPE(ARENA)
 
index 9bd8d5e32f4d3a8fd116054977b071aaf1ae3404..d8c0bccac7f575ad46c092cfe28738fb857a0e42 100644 (file)
@@ -263,6 +263,7 @@ void Send_Notification_WOVA(
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FALL,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_fall",          _("^BG%s%s^K1 was grounded by ^BG%s^K1%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FIRE,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was burnt up into a crisp by ^BG%s^K1%s%s\n"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s\n")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_LAVA,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_lava",          _("^BG%s%s^K1 was cooked by ^BG%s^K1%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_MONSTER,           3, 2, "spree_inf s1 s2 s3loc spree_end", "",       "",                             _("^BG%s%s^K1 was pushed infront of a monster by ^BG%s^K1%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SHOOTING_STAR,     3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_shootingstar",  _("^BG%s%s^K1 was shot into space by ^BG%s^K1%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SLIME,             3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_slime",         _("^BG%s%s^K1 was slimed by ^BG%s^K1%s%s\n"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SWAMP,             3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_slime",         _("^BG%s%s^K1 was preserved by ^BG%s^K1%s%s\n"), "") \
@@ -291,6 +292,29 @@ 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_CERBERUS_BITE,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was mauled by a Cerberus%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_CERBERUS_JUMP,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 didn't see the pouncing Cerberus%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ANIMUS,          2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was eviscerated by an Animus%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_STINGRAY,        2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was fatally wounded by a Stingray%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_KNIGHT_CRUSH,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was crushed by a pouncing Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_KNIGHT_FBALL,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was turned to ash by a Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_KNIGHT_INFERNO,  2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was burned to death by a Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_KNIGHT_MELEE,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was slain by a Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_KNIGHT_SPIKE,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was cursed by a Knight%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_BRUISER,             2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was beaten in a fistfight by a Bruiser%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_BRUTE_BLADE,     2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was cut down by a Brute%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_BRUTE_GRENADE,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 almost dodged a Brute's grenade%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_BRUTE_UZI,           2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was nailed by a Brute%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_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_SPIDER_FIRE,     2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was incinerated by a Spider%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SLIME,           2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was blown up by a Slime%s%s\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_WYVERN,          2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was fireballed by a Wyvern%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"), "") \
@@ -348,6 +372,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"), "") \
@@ -367,6 +392,27 @@ 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, "", "",                            "",                     _("^BGThis turret is already at max health\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_MAXPOWER,                        0, 0, "", "",                            "",                         _("^BGThis turret is already at max power\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_MAXTURRETS,                      0, 1, "f1", "",                          "",                         _("^BGYou can't spawn more than %s turrets\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_NOFUEL,                          0, 0, "", "",                            "",                     _("^BGYou don't have enough fuel to spawn that turret\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_NOFUEL_REPAIR,                   0, 1, "f1", "",                          "",                         _("^BGYou need %s fuel to repair this turret\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_NOFUEL_UPGRADE,              0, 1, "f1", "",                          "",                     _("^BGYou need %s fuel to increase this turret's power\n"), "") \
+       MSG_INFO_NOTIF(1, INFO_TD_PHASE_BUILD,                     0, 3, "f1 f2 f3", "",                    "",                     _("^BGWave ^F2%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_DISABLED,                        0, 0, "", "",                            "",                         _("^BGTurrets are disabled on this map\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"), "") \
@@ -473,6 +519,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!"), "") \
@@ -548,6 +595,29 @@ 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_AIM_REMOVE,               0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGYou need to aim at your turret to remove it"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_AIM_REPAIR,               0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGYou need to aim at your turret to repair it"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_AIM_UPGRADE,              0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGYou need to aim at your turret to upgrade it"), "") \
+       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_CANTSPAWN,                        0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGYou can't currently spawn a turret"), "") \
+       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_LIST,                             1, 0, "s1",            CPID_TOWERDEFENSE,     "0 0", _("^BGAvilable turrets: ^F2%s"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_MAXHEALTH,                        0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGThis turret is already at max health"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_MAXPOWER,                         0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGThis turret is already at max power"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_MAXTURRETS,               0, 1, "f1",            CPID_TOWERDEFENSE,     "0 0", _("^BGYou can't spawn more than %s turrets"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_NOFUEL,                           0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGYou don't have enough fuel to spawn that turret"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_NOFUEL_REPAIR,            0, 1, "f1",            CPID_TOWERDEFENSE,     "0 0", _("^BGYou need %s fuel to repair this turret"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_NOFUEL_UPGRADE,           0, 1, "f1",            CPID_TOWERDEFENSE,     "0 0", _("^BGYou need %s fuel to increase this turret's power"), "") \
+       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\n^BG wave starts in ^F2%s seconds"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_PHASE_COMBAT,             0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^K1Combat phase\n^K2Protect the generator from monsters!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_PROTECT,                  0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGProtect the generator from waves of monsters!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_REMOVE,                           0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGTurret removed"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_REPAIR,                           0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^F1Turret repaired by 100 health points!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_SPAWN,                            0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGYou spawned a turret"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_DISABLED,                         0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^BGTurrets are disabled on this map"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_TD_UPGRADE,                          0, 0, "",              CPID_TOWERDEFENSE,     "0 0", _("^F1Turret power increased by 20 percent!"), "") \
+       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"), "") \
@@ -561,6 +631,7 @@ void Send_Notification_WOVA(
        MSG_MULTI_NOTIF(1, DEATH_MURDER_FALL,                    NO_MSG,        INFO_DEATH_MURDER_FALL,                    NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_FIRE,                    NO_MSG,        INFO_DEATH_MURDER_FIRE,                    NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_LAVA,                    NO_MSG,        INFO_DEATH_MURDER_LAVA,                    NO_MSG) \
+       MSG_MULTI_NOTIF(1, DEATH_MURDER_MONSTER,                 NO_MSG,        INFO_DEATH_MURDER_MONSTER,                 CENTER_DEATH_SELF_MONSTER) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_SHOOTING_STAR,           NO_MSG,        INFO_DEATH_MURDER_SHOOTING_STAR,           NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_SLIME,                   NO_MSG,        INFO_DEATH_MURDER_SLIME,                   NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_SWAMP,                   NO_MSG,        INFO_DEATH_MURDER_SWAMP,                   NO_MSG) \
@@ -589,6 +660,29 @@ void Send_Notification_WOVA(
        MSG_MULTI_NOTIF(1, DEATH_SELF_FIRE,                      NO_MSG,        INFO_DEATH_SELF_FIRE,                      CENTER_DEATH_SELF_FIRE) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_GENERIC,                   NO_MSG,        INFO_DEATH_SELF_GENERIC,                   CENTER_DEATH_SELF_GENERIC) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_LAVA,                      NO_MSG,        INFO_DEATH_SELF_LAVA,                      CENTER_DEATH_SELF_LAVA) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_CERBERUS_BITE,                 NO_MSG,        INFO_DEATH_SELF_MON_CERBERUS_BITE,                 CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_CERBERUS_JUMP,                 NO_MSG,        INFO_DEATH_SELF_MON_CERBERUS_JUMP,                 CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ANIMUS,                                NO_MSG,        INFO_DEATH_SELF_MON_ANIMUS,                                CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_STINGRAY,                              NO_MSG,        INFO_DEATH_SELF_MON_STINGRAY,                      CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_KNIGHT_CRUSH,                  NO_MSG,        INFO_DEATH_SELF_MON_KNIGHT_CRUSH,                  CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_KNIGHT_FBALL,              NO_MSG,        INFO_DEATH_SELF_MON_KNIGHT_FBALL,              CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_KNIGHT_INFERNO,                NO_MSG,        INFO_DEATH_SELF_MON_KNIGHT_INFERNO,            CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_KNIGHT_MELEE,              NO_MSG,        INFO_DEATH_SELF_MON_KNIGHT_MELEE,              CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_KNIGHT_SPIKE,              NO_MSG,        INFO_DEATH_SELF_MON_KNIGHT_SPIKE,              CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_BRUISER,                               NO_MSG,        INFO_DEATH_SELF_MON_BRUISER,                       CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_BRUTE_BLADE,                   NO_MSG,        INFO_DEATH_SELF_MON_BRUTE_BLADE,                   CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_BRUTE_GRENADE,                 NO_MSG,        INFO_DEATH_SELF_MON_BRUTE_GRENADE,                 CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_BRUTE_UZI,                     NO_MSG,        INFO_DEATH_SELF_MON_BRUTE_UZI,                     CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_MAGE,                                  NO_MSG,        INFO_DEATH_SELF_MON_MAGE,                                  CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_CLAW,                 NO_MSG,        INFO_DEATH_SELF_MON_SHAMBLER_CLAW,                 CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_SMASH,                NO_MSG,        INFO_DEATH_SELF_MON_SHAMBLER_SMASH,                CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_ZAP,                  NO_MSG,        INFO_DEATH_SELF_MON_SHAMBLER_ZAP,                  CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SPIDER,                                NO_MSG,        INFO_DEATH_SELF_MON_SPIDER,                                CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SPIDER_FIRE,                   NO_MSG,        INFO_DEATH_SELF_MON_SPIDER_FIRE,                   CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SLIME,                                 NO_MSG,        INFO_DEATH_SELF_MON_SLIME,                                 CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_WYVERN,                                NO_MSG,        INFO_DEATH_SELF_MON_WYVERN,                                CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_JUMP,                   NO_MSG,        INFO_DEATH_SELF_MON_ZOMBIE_JUMP,                   CENTER_DEATH_SELF_MONSTER) \
+       MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_MELEE,                  NO_MSG,        INFO_DEATH_SELF_MON_ZOMBIE_MELEE,                  CENTER_DEATH_SELF_MONSTER) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_NOAMMO,                    NO_MSG,        INFO_DEATH_SELF_NOAMMO,                    CENTER_DEATH_SELF_NOAMMO) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_ROT,                       NO_MSG,        INFO_DEATH_SELF_ROT,                       CENTER_DEATH_SELF_ROT) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_SHOOTING_STAR,             NO_MSG,        INFO_DEATH_SELF_SHOOTING_STAR,             CENTER_DEATH_SELF_SHOOTING_STAR) \
@@ -627,6 +721,26 @@ void Send_Notification_WOVA(
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_UNAVAILABLE,              NO_MSG,        INFO_ITEM_WEAPON_UNAVAILABLE,              CENTER_ITEM_WEAPON_UNAVAILABLE) \
        MSG_MULTI_NOTIF(1, MULTI_COUNTDOWN_BEGIN,                ANNCE_BEGIN,   NO_MSG,                                    CENTER_COUNTDOWN_BEGIN) \
        MSG_MULTI_NOTIF(1, MULTI_MINSTA_FINDAMMO,                ANNCE_NUM_10,  NO_MSG,                                    CENTER_MINSTA_FINDAMMO_FIRST) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_AIM_REMOVE,                  NO_MSG,                INFO_TD_AIM_REMOVE,                                CENTER_TD_AIM_REMOVE) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_AIM_REPAIR,                  NO_MSG,                INFO_TD_AIM_REPAIR,                                CENTER_TD_AIM_REPAIR) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_AIM_UPGRADE,                 NO_MSG,                INFO_TD_AIM_UPGRADE,                       CENTER_TD_AIM_UPGRADE) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_CANTSPAWN,                   NO_MSG,                INFO_TD_CANTSPAWN,                                 CENTER_TD_CANTSPAWN) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_DISABLED,                            NO_MSG,                INFO_TD_DISABLED,                                  CENTER_TD_DISABLED) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_GENDESTROYED,                NO_MSG,                INFO_TD_GENDESTROYED,                              CENTER_TD_GENDESTROYED) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_LIST,                                NO_MSG,                INFO_TD_LIST,                                      CENTER_TD_LIST) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_MAXHEALTH,                   NO_MSG,                INFO_TD_MAXHEALTH,                                 CENTER_TD_MAXHEALTH) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_MAXPOWER,                            NO_MSG,                INFO_TD_MAXPOWER,                                  CENTER_TD_MAXPOWER) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_MAXTURRETS,                  NO_MSG,                INFO_TD_MAXTURRETS,                                CENTER_TD_MAXTURRETS) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL,                              NO_MSG,                INFO_TD_NOFUEL,                                    CENTER_TD_NOFUEL) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL_REPAIR,               NO_MSG,                INFO_TD_NOFUEL_REPAIR,                     CENTER_TD_NOFUEL_REPAIR) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL_UPGRADE,              NO_MSG,                INFO_TD_NOFUEL_UPGRADE,                    CENTER_TD_NOFUEL_UPGRADE) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_BUILD,                 NO_MSG,                INFO_TD_PHASE_BUILD,                               CENTER_TD_PHASE_BUILD) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_COMBAT,                NO_MSG,                INFO_TD_PHASE_COMBAT,                              CENTER_TD_PHASE_COMBAT) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_REMOVE,                              NO_MSG,                INFO_TD_REMOVE,                                    CENTER_TD_REMOVE) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_REPAIR,                              NO_MSG,                INFO_TD_REPAIR,                                    CENTER_TD_REPAIR) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_SPAWN,                               NO_MSG,                INFO_TD_SPAWN,                                     CENTER_TD_SPAWN) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_UPGRADE,                         NO_MSG,            INFO_TD_UPGRADE,                                   CENTER_TD_UPGRADE) \
+       MSG_MULTI_NOTIF(1, MULTI_TD_VICTORY,                             NO_MSG,                INFO_TD_VICTORY,                                   CENTER_TD_VICTORY) \
        MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_MURDER,              NO_MSG,        INFO_WEAPON_ACCORDEON_MURDER,              NO_MSG) \
        MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_SUICIDE,             NO_MSG,        INFO_WEAPON_ACCORDEON_SUICIDE,             CENTER_DEATH_SELF_GENERIC) \
        MSG_MULTI_NOTIF(1, WEAPON_CRYLINK_MURDER,                NO_MSG,        INFO_WEAPON_CRYLINK_MURDER,                NO_MSG) \
index f00971674ef8bca06675809fcc1526744450969b..f82e971a739005a56ebd5ed4366759528553208a 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..8a07ed6
--- /dev/null
@@ -0,0 +1,64 @@
+#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", "brute", _("Brute")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "animus", _("Animus")));
+               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", "bruiser", _("Bruiser")));
+               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", "shambler", _("Shambler")));
+       me.TR(me);
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "cerberus", _("Cerberus")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "slime", _("Slime")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "stingray", _("Stingray")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "mage", _("Mage")));
+               me.TD(me, 1, 0.4, e = makeXonoticRadioButton(2, "menu_monsters_edit_spawn", "wyvern", _("Wyvern")));
+       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.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 b4ca21385d3d744652d9e68ce7acec9a5e22be17..155c3bd4b887a831ccc729d28d9376c36a985b2d 100644 (file)
@@ -610,6 +610,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_RACE) \
        GAMETYPE(MAPINFO_TYPE_CTS) \
        GAMETYPE(MAPINFO_TYPE_TEAM_DEATHMATCH) \
+       GAMETYPE(MAPINFO_TYPE_TD) \
        /* nothing */
 
 float GameType_GetID(float cnt)
index 3c737f6e6cec277f4e8d225f6ccefcc0004a271d..d4b706dca15972e0c74cad233713cc12a2bed33b 100644 (file)
@@ -109,8 +109,11 @@ void accuracy_add(entity e, float w, float fired, float hit)
 
 float accuracy_isgooddamage(entity attacker, entity targ)
 {
+       float targ_isvalid = ((g_td) ? targ.flags & FL_MONSTER : IS_CLIENT(targ));
+       
        if(!inWarmupStage)
-       if(IS_CLIENT(targ))
+       if(targ_isvalid)
+       if not(attacker.flags & FL_MONSTER) // no accuracy for monsters
        if(targ.deadflag == DEAD_NO)
        if(IsDifferentTeam(attacker, targ))
                return TRUE;
index dead5a9d4b360d924a0642f4b21fe0161e734ccd..95efd27229347046c321294ba0e18fad0cf225df 100644 (file)
@@ -732,6 +732,7 @@ float autocvar_g_chat_teamcolors;
 float autocvar_g_chat_tellprivacy;
 float autocvar_g_ctf_allow_vehicle_carry;
 float autocvar_g_ctf_allow_vehicle_touch;
+float autocvar_g_ctf_allow_monster_touch;
 float autocvar_g_ctf_throw;
 float autocvar_g_ctf_throw_angle_max;
 float autocvar_g_ctf_throw_angle_min;
@@ -1230,7 +1231,32 @@ float autocvar_physics_ode;
 float autocvar_g_physical_items;
 float autocvar_g_physical_items_damageforcescale;
 float autocvar_g_physical_items_reset;
+float autocvar_g_monsters;
+float autocvar_g_monsters_think_delay;
+float autocvar_g_monsters_max;
+float autocvar_g_monsters_max_perplayer;
+float autocvar_g_monsters_target_range;
+float autocvar_g_monsters_target_infront;
+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;
+float autocvar_g_monsters_drop_time;
+string autocvar_g_monsters_drop_type;
+string autocvar_g_monsters_drop_size;
+float autocvar_g_monsters_spawnshieldtime;
+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_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;
 float autocvar_g_touchexplode_radius;
 float autocvar_g_touchexplode_damage;
 float autocvar_g_touchexplode_edgedamage;
 float autocvar_g_touchexplode_force;
+float autocvar_g_td_debug;
index 578306c4eb89c49c6e69707f07c6750273965601..3dd0b84543b6c83f0cdad70b90ca0a35762a6ce1 100644 (file)
@@ -111,7 +111,7 @@ float bot_shouldattack(entity e)
                        return FALSE;
        }
 
-       if(e.freezetag_frozen)
+       if(e.frozen)
                return FALSE;
 
        // If neither player has ball then don't attack unless the ball is on the
index c687c4502c33ae7c7ec9c8c69fc95559f6d2a788..41f8f911728ad29cf474bb76357f794f662aa395 100644 (file)
@@ -168,6 +168,8 @@ void PutObserverInServer (void)
 
        Portal_ClearAll(self);
        
+       Unfreeze(self);
+       
        if(self.alivetime)
        {
                if(!inWarmupStage)
@@ -532,6 +534,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;
@@ -577,6 +580,8 @@ void PutClientInServer (void)
                                self.target = s;
                        activator = world;
                self = oldself;
+               
+               Unfreeze(self);
 
                spawn_spot = spot;
                MUTATOR_CALLHOOK(PlayerSpawn);
@@ -927,7 +932,7 @@ void ClientKill (void)
 {
        if(gameover) return;
        if(self.player_blocked) return;
-       if(self.freezetag_frozen) return;
+       if(self.frozen) return;
        
        ClientKill_TeamChange(0);
 }
@@ -1277,6 +1282,8 @@ void ClientDisconnect (void)
        MUTATOR_CALLHOOK(ClientDisconnect);
 
        Portal_ClearAll(self);
+       
+       Unfreeze(self);
 
        RemoveGrapplingHook(self);
 
@@ -1728,6 +1735,8 @@ void SpectateCopy(entity spectatee) {
        self.dmg_inflictor = spectatee.dmg_inflictor;
        self.v_angle = spectatee.v_angle;
        self.angles = spectatee.v_angle;
+       self.frozen = spectatee.frozen;
+       self.revive_progress = spectatee.revive_progress;
        if(!self.BUTTON_USE)
                self.fixangle = TRUE;
        setorigin(self, spectatee.origin);
@@ -2238,6 +2247,16 @@ void PlayerPreThink (void)
                return;
 #endif
 
+       if(self.frozen == 2)
+       {
+               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);
+               self.iceblock.alpha = 1 - self.revive_progress;
+
+               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
@@ -2359,7 +2378,7 @@ void PlayerPreThink (void)
                        do_crouch = 1;
                if(self.vehicle)
                        do_crouch = 0;
-               if(self.freezetag_frozen)
+               if(self.frozen)
                        do_crouch = 0;
                if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
                        do_crouch = 0;
@@ -2431,6 +2450,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 5ce7b7d475f4d6848442b8fa9761106e5927ed2c..4af0fdb43b09ffc0f4491e2ea8fd1f8d117201e5 100644 (file)
@@ -23,7 +23,7 @@ When you press the jump key
 */
 void PlayerJump (void)
 {
-       if(self.freezetag_frozen)
+       if(self.frozen)
                return; // no jumping in freezetag when frozen
 
        float mjumpheight;
@@ -847,6 +847,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);
 
@@ -1049,7 +1055,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.frozen)
        {
                //makevectors(self.v_angle_y * '0 1 0');
                makevectors(self.v_angle);
index 68a2de343901fba60b3c926e7a9a42121cc68e69..04ac977f433be4bcc63bf8d41e29cc95f8c0c2eb 100644 (file)
@@ -245,7 +245,7 @@ void player_anim (void)
                else
                        deadbits = ANIMSTATE_DEAD2;
        float animbits = deadbits;
-       if(self.freezetag_frozen)
+       if(self.frozen)
                animbits |= ANIMSTATE_FROZEN;
        if(self.crouch)
                animbits |= ANIMSTATE_DUCK;
index 34f17a613a7f438f566b6d47859463f50729ac4b..df57ce9b999c67b21bef1f8b93a85cfc84f57f4b 100644 (file)
@@ -330,6 +330,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)
@@ -358,7 +360,7 @@ float forbidWeaponUse()
                return 1;
        if(self.player_blocked)
                return 1;
-       if(self.freezetag_frozen)
+       if(self.frozen)
                return 1;
        return 0;
 }
index 26af874e2afdb88abe26f2532a55a89b79622969..b4a978d33208599628a109968b1f31531170f681 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)
@@ -1014,6 +1014,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;
@@ -1277,6 +1279,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 ec9c33b1987b093005f28c5df16c062d3d9e9c65..f8e27bb469b283e8ec69f92e8a65206edcaf7eb3 100644 (file)
@@ -182,6 +182,138 @@ 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))); if(trace_ent.sprite) WaypointSprite_UpdateSprites(trace_ent.sprite, trace_ent.netname, "", ""); return;
+                               case "skin": if(trace_ent.monsterid != MONSTER_MAGE) { trace_ent.skin = stof(argv(2)); trace_ent.SendFlags |= MSF_STATUS; } return;
+                               case "movetarget": trace_ent.monster_moveflags = stof(argv(2)); return;
+                       }
+               }
+               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, 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 + 200, 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 = strtolower(argv(1));
+                       mname = argv(3);
+                       
+                       if(tospawn == "list")
+                       {
+                               float i;
+                               string list = "Available monsters:";
+                               
+                               for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
+                                       list = strcat(list, " ", monster_id2string(i));
+                               
+                               sprint(self, strcat(list, "\n"));
+                               
+                               return;
+                       }
+                       
+                       if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); }
+                       else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); }
+                       else if not(autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); }
+                       else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); }
+                       else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\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_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
+                               //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
+                       
+                               e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
+                               if(mname) e.netname = strzone(mname);
+                               
+                               sprint(self, strcat("Spawned ", e.netname, "\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 +696,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..45d1f7584cef1663b5d6909d67714092360eaea5 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, float mnster, 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 ba9b48a294a7ea5f9bc4bbda9eff993f0c8168b2..b7256deed443aafafa2dc3c76b53a3bf79ff2245 100644 (file)
@@ -139,6 +139,58 @@ 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; }
+                       if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; }
+               
+            float removed_count = 0;
+                       entity montokill, head;
+                       
+            FOR_EACH_MONSTER(montokill)
+            {
+                               WaypointSprite_Kill(montokill.sprite);
+                               
+                               if(montokill.weaponentity)
+                                       remove(montokill.weaponentity);
+                                       
+                               if(montokill.iceblock)
+                                       remove(montokill.iceblock);
+                    
+                remove(montokill);
+                removed_count += 1;
+            }
+                       
+                       FOR_EACH_PLAYER(head)
+                               head.monstercount = 0;
+                               
+                       monsters_total = 0; // reset stats?
+                       monsters_killed = 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)
@@ -1723,6 +1775,7 @@ void GameCommand_(float request)
 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
 #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 7d3585725d2544ecd4dab000d907c51cbab6354e..43755d94238cce6c936127f4a2e27e066d868f8c 100644 (file)
@@ -582,7 +582,10 @@ float serverflags;
 
 .float player_blocked;
 
-.float freezetag_frozen;
+.float frozen; // for freeze attacks
+.float revive_progress;
+.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
+.entity iceblock;
 
 .entity muzzle_flash;
 .float misc_bulletcounter;     // replaces uzi & hlac bullet counter.
index da013fc75d0eebe984ebc4a39717489d47d72f45..7ca31cecbb91adc8f24e61dc755bff041fc985ba 100644 (file)
@@ -544,6 +544,71 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype)
        if(targ.killcount) { targ.killcount = 0; }
 }
 
+void Ice_Think()
+{
+       setorigin(self, self.owner.origin - '0 0 16');
+       self.nextthink = time;
+}
+
+void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
+{
+       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)
+               return;
+               
+       targ.frozen = frozen_type;
+       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");
+       ice.alpha = 1;
+       ice.colormod = Team_ColorRGB(targ.team);
+       ice.glowmod = ice.colormod;
+       targ.iceblock = ice;
+
+       entity oldself;
+       oldself = self;
+       self = ice;
+       Ice_Think();
+       self = oldself;
+
+       RemoveGrapplingHook(targ);
+       
+       // add waypoint
+       if(show_waypoint)       
+               WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
+}
+
+void Unfreeze (entity targ)
+{
+       if not(targ.frozen)
+               return; // not even frozen?
+               
+       targ.frozen = 0;
+       targ.revive_progress = 0;
+       targ.health = ((targ.classname == STR_PLAYER) ? autocvar_g_balance_health_start : targ.max_health);
+       
+       WaypointSprite_Kill(targ.waypointsprite_attached);
+
+       // remove the ice block
+       remove(targ.iceblock);
+       targ.iceblock = world;
+}
+
 // these are updated by each Damage call for use in button triggering and such
 entity damage_targ;
 entity damage_inflictor;
@@ -672,6 +737,12 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        mirrorforce *= g_weaponforcefactor;
                }
                
+               if(((targ.frozen == 2 && attacker.monsterid != MONSTER_SPIDER) || (targ.frozen == 1)) && deathtype != DEATH_HURTTRIGGER)
+               {
+                       damage = 0;
+                       force *= 0.2;
+               }
+               
                // should this be changed at all? If so, in what way?
                frag_attacker = attacker;
                frag_target = targ;
@@ -726,7 +797,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        else
                                victim = targ;
 
-                       if(IS_PLAYER(victim) || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+                       if(IS_PLAYER(victim) || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET || victim.flags & FL_MONSTER)
                        {
                                if(IsDifferentTeam(victim, attacker))
                                {
@@ -1194,7 +1265,7 @@ void Fire_ApplyDamage(entity e)
                e.fire_endtime = 0;
 
        // ice stops fire
-       if(e.freezetag_frozen)
+       if(e.frozen)
                e.fire_endtime = 0;
 
        t = min(frametime, e.fire_endtime - time);
index 3043a30003a590f713fc6561074605fbf31d236f..cf2d12612e27e0a648de1d32fcd1df620a618ee9 100644 (file)
@@ -266,6 +266,7 @@ void cvar_changes_init()
                BADCVAR("g_freezetag_teams");
                BADCVAR("g_keepaway");
                BADCVAR("g_keyhunt");
+               BADCVAR("g_td");
                BADCVAR("g_keyhunt_teams");
                BADCVAR("g_lms");
                BADCVAR("g_nexball");
@@ -799,6 +800,10 @@ void spawnfunc_worldspawn (void)
        addstat(STAT_NEX_CHARGEPOOL, AS_FLOAT, nex_chargepool_ammo);
 
        addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load);
+       
+       // 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);
@@ -809,6 +814,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);
diff --git a/qcsrc/server/generator.qc b/qcsrc/server/generator.qc
new file mode 100644 (file)
index 0000000..62fd76e
--- /dev/null
@@ -0,0 +1,355 @@
+#ifdef CSQC
+float generator_precached;
+.float count;
+
+vector randompos(vector m1, vector m2)
+{
+    vector v;
+    m2 = m2 - m1;
+    v_x = m2_x * random() + m1_x;
+    v_y = m2_y * random() + m1_y;
+    v_z = m2_z * random() + m1_z;
+    return  v;
+}
+
+void generator_precache()
+{
+       if(generator_precached)
+               return; // already precached
+               
+       precache_model("models/onslaught/generator.md3");
+       precache_model("models/onslaught/generator_dead.md3");
+       precache_model("models/onslaught/generator_dmg1.md3");
+       precache_model("models/onslaught/generator_dmg2.md3");
+       precache_model("models/onslaught/generator_dmg3.md3");
+       precache_model("models/onslaught/generator_dmg4.md3");
+       precache_model("models/onslaught/generator_dmg5.md3");
+       precache_model("models/onslaught/generator_dmg6.md3");
+       precache_model("models/onslaught/generator_dmg7.md3");
+       precache_model("models/onslaught/generator_dmg8.md3");
+       precache_model("models/onslaught/generator_dmg9.md3");
+       precache_model("models/onslaught/generator_dead.md3");
+       
+       precache_model("models/onslaught/ons_ray.md3");
+       precache_sound("onslaught/shockwave.wav");
+       precache_sound("weapons/grenade_impact.wav");
+       precache_sound("weapons/rocket_impact.wav");
+       
+       precache_model("models/onslaught/gen_gib1.md3");
+       precache_model("models/onslaught/gen_gib2.md3");
+       precache_model("models/onslaught/gen_gib3.md3");
+       
+       generator_precached = TRUE;
+}
+
+void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce)
+{
+       self.velocity = self.velocity + vforce;
+}
+
+.float giblifetime;
+
+void gib_draw_noburn()
+{
+       if(time >= self.giblifetime)
+               remove(self);
+}
+
+void gib_draw()
+{
+       if(time >= self.move_time)
+               return;
+
+       self.move_time = time + 0.05;
+
+       if(time > self.giblifetime)
+       {
+               remove(self);
+               return;
+       }
+       
+       self.alpha -= 0.05;
+       
+       if(self.alpha < 0.1)
+       {
+               remove(self);
+               return;
+       }
+
+       if(random()<0.6)
+               pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1);
+}
+
+void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn)
+{
+       entity gib;
+
+       gib = spawn();
+
+       setmodel(gib, smodel);
+       setorigin(gib, v_from);
+       gib.solid = SOLID_CORPSE;
+       gib.move_movetype = MOVETYPE_BOUNCE;
+       gib.movetype = MOVETYPE_BOUNCE;
+       gib.health = 255;
+       gib.move_velocity = v_to;
+       gib.move_origin = v_from;
+       gib.velocity = v_to;
+       gib.alpha = 1;
+       gib.move_time = time;
+       gib.drawmask = MASK_NORMAL;
+       gib.giblifetime = time + f_lifetime;
+       
+       if(b_burn)
+               gib.draw = gib_draw;
+       else
+               gib.draw = gib_draw_noburn;     
+}
+
+void onslaught_generator_ray_think()
+{
+       self.nextthink = time + 0.05;
+       if(self.count > 10)
+       {
+               self.think = SUB_Remove;
+               return;
+       }
+
+       if(self.count > 5)
+               self.alpha -= 0.1;
+       else
+               self.alpha += 0.1;
+
+       self.scale += 0.2;
+       self.count +=1;
+}
+
+void onslaught_generator_ray_spawn(vector org)
+{
+       entity e;
+       e = spawn();
+       setmodel(e, "models/onslaught/ons_ray.md3");
+       setorigin(e, org);
+       e.angles = randomvec() * 360;
+       e.alpha = 0;
+       e.scale = random() * 5 + 8;
+       e.think = onslaught_generator_ray_think;
+       e.nextthink = time + 0.05;
+}
+
+void generator_draw()
+{
+       if(self.health > 0)
+               return;
+         
+       if(time < self.move_time)
+               return;
+       if(self.count <= 0)
+               return;
+    
+    vector org;
+       float i;
+
+       // White shockwave
+       if(self.count==40||self.count==20)
+       {
+               sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTN_NORM);
+               pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 6);
+       }
+
+       // Throw some gibs
+       if(random() < 0.3)
+       {
+               i = random();
+               if(i < 0.3)
+                       ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, TRUE);
+               else if(i > 0.7)
+                       ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, TRUE);
+               else
+                       ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, TRUE);
+       }
+
+       // Spawn fire balls
+       for(i=0;i < 10;++i)
+       {
+               org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20');
+               pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1);
+       }
+
+       // Short explosion sound + small explosion
+       if(random() < 0.25)
+       {
+               te_explosion(self.origin);
+               sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+       }
+
+       // Particles
+       org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8');
+       pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
+
+       // rays
+       if(random() > 0.25 )
+       {
+               onslaught_generator_ray_spawn(self.origin);
+       }
+
+       // Final explosion
+       if(self.count==1)
+       {
+               org = self.origin;
+               te_explosion(org);
+               pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
+               sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+       }
+       
+       self.move_time = time + 0.05;
+
+       self.count -= 1;
+}
+
+.float max_health;
+void generator_damage(float hp)
+{
+       if(hp <= 0)
+               setmodel(self, "models/onslaught/generator_dead.md3");
+       else if(hp < self.max_health * 0.10)
+               setmodel(self, "models/onslaught/generator_dmg9.md3");
+       else if(hp < self.max_health * 0.20)
+               setmodel(self, "models/onslaught/generator_dmg8.md3");
+       else if(hp < self.max_health * 0.30)
+               setmodel(self, "models/onslaught/generator_dmg7.md3");
+       else if(hp < self.max_health * 0.40)
+               setmodel(self, "models/onslaught/generator_dmg6.md3");
+       else if(hp < self.max_health * 0.50)
+               setmodel(self, "models/onslaught/generator_dmg5.md3");
+       else if(hp < self.max_health * 0.60)
+               setmodel(self, "models/onslaught/generator_dmg4.md3");
+       else if(hp < self.max_health * 0.70)
+               setmodel(self, "models/onslaught/generator_dmg3.md3");
+       else if(hp < self.max_health * 0.80)
+               setmodel(self, "models/onslaught/generator_dmg2.md3");
+       else if(hp < self.max_health * 0.90)
+               setmodel(self, "models/onslaught/generator_dmg1.md3");
+       else if(hp <= self.max_health || hp >= self.max_health)
+               setmodel(self, "models/onslaught/generator.md3");
+               
+       setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+}
+
+void generator_construct()
+{
+       self.netname = "Generator";
+
+       setorigin(self, self.origin);
+       setmodel(self, "models/onslaught/generator.md3");
+       setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+       
+       self.move_movetype      = MOVETYPE_NOCLIP;
+       self.solid                      = SOLID_BBOX;
+       self.movetype           = MOVETYPE_NOCLIP; 
+       self.move_origin        = self.origin;
+       self.move_time          = time;
+       self.drawmask           = MASK_NORMAL;  
+       self.alpha                      = 1;
+       self.draw                       = generator_draw;
+}
+
+.vector glowmod;
+void generator_changeteam()
+{
+       if(self.team)
+       {
+               self.glowmod = Team_ColorRGB(self.team - 1);
+               self.teamradar_color = Team_ColorRGB(self.team - 1);
+               self.colormap = 1024 + (self.team - 1) * 17;
+       }
+       else
+       {
+               self.colormap = 1024;
+               self.glowmod = '1 1 0';
+               self.teamradar_color = '1 1 0';
+       }
+}
+
+void ent_generator()
+{
+       float sf;
+       sf = ReadByte();
+
+       if(sf & GSF_SETUP)
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
+               
+               self.health = ReadByte();
+               self.max_health = ReadByte();
+               self.count = ReadByte();
+               self.team = ReadByte();
+               
+               if not(self.count)
+                       self.count = 40;
+               
+               generator_changeteam();
+               generator_precache();
+               generator_construct();
+       }
+
+       if(sf & GSF_STATUS)
+       {
+               float _tmp;
+               _tmp = ReadByte();
+               if(_tmp != self.team)
+               {                       
+                       self.team = _tmp;                               
+                       generator_changeteam();
+               }
+               
+               _tmp = ReadByte();
+               
+               if(_tmp != self.health)
+                       generator_damage(_tmp);
+
+               self.health = _tmp;
+       }
+}
+#endif // CSQC
+
+#ifdef SVQC
+float generator_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_GENERATOR);    
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & GSF_SETUP)
+       {
+           WriteCoord(MSG_ENTITY, self.origin_x);
+           WriteCoord(MSG_ENTITY, self.origin_y);
+           WriteCoord(MSG_ENTITY, self.origin_z);
+               
+               WriteByte(MSG_ENTITY, self.health);
+               WriteByte(MSG_ENTITY, self.max_health);
+               WriteByte(MSG_ENTITY, self.count);
+               WriteByte(MSG_ENTITY, self.team);
+    }
+    
+    if(sf & GSF_STATUS)
+    {
+               WriteByte(MSG_ENTITY, self.team);
+       
+        if(self.health <= 0)
+            WriteByte(MSG_ENTITY, 0);
+        else
+            WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
+    }
+    
+       return TRUE;
+}
+
+void generator_link(void() spawnproc)
+{
+    Net_LinkEntity(self, TRUE, 0, generator_send);
+    self.think      = spawnproc;
+    self.nextthink  = time;
+}
+#endif // SVQC
diff --git a/qcsrc/server/generator.qh b/qcsrc/server/generator.qh
new file mode 100644 (file)
index 0000000..439a342
--- /dev/null
@@ -0,0 +1,10 @@
+const vector GENERATOR_MIN = '-52 -52 -14';
+const vector GENERATOR_MAX = '52 52 75';
+
+float GSF_STATUS = 4;
+float GSF_SETUP = 8;
+
+#ifdef CSQC
+void ent_generator();
+void generator_precache();
+#endif
\ No newline at end of file
index df09032139b62e7158c6ef20d95a841b037c7c9a..869402eba5693ca9070671aca5a9cef8ef2cfdb3 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)
@@ -942,6 +944,8 @@ void readlevelcvars(void)
     g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
     g_bugrigs_steer = cvar("g_bugrigs_steer");
        
+       monster_skill = cvar("g_monsters_skill");
+       
        g_minstagib = cvar("g_minstagib");
 
        sv_clones = cvar("sv_clones");
diff --git a/qcsrc/server/monsters/lib/defs.qh b/qcsrc/server/monsters/lib/defs.qh
new file mode 100644 (file)
index 0000000..f5a75e3
--- /dev/null
@@ -0,0 +1,52 @@
+.float(float attack_type) monster_attackfunc;
+const float MONSTER_ATTACK_MELEE       = 1;
+const float MONSTER_ATTACK_RANGED      = 2;
+
+.float candrop;
+
+.float attack_range;
+
+.float spawn_time; // stop monster from moving around right after spawning
+
+.string oldtarget2;
+.float lastshielded;
+
+const float MONSTER_RESPAWN_DEATHPOINT = 8; // re-spawn where we died
+
+.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
+
+// new flags
+const float MONSTERFLAG_MINIBOSS = 1;  // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
+const float MONSTERFLAG_NORESPAWN = 4;
+const float MONSTERFLAG_SPAWNED = 512; // 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;
+
+.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
+
+const float MONSTER_STATE_ATTACK_LEAP = 1;
+const float MONSTER_STATE_ATTACK_MELEE = 2;
diff --git a/qcsrc/server/monsters/lib/monsters.qc b/qcsrc/server/monsters/lib/monsters.qc
new file mode 100644 (file)
index 0000000..9bc60b9
--- /dev/null
@@ -0,0 +1,1052 @@
+// TODO: clean up this file?
+
+void() spawnfunc_item_minst_cells;
+
+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_item_spawn()
+{
+       self.monster_loot();
+       
+       self.gravity = 1;
+       self.velocity = randomvec() * 175 + '0 0 325';
+       self.touch = M_Item_Touch;
+       
+       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
+}
+
+void Monster_DropItem (string itype, string itemsize)
+{
+       vector org = self.origin + ((self.mins + self.maxs) * 0.5);
+       entity e = spawn();
+       
+       setorigin(e, org);
+       
+       switch(itype)
+       {
+               case "armor":
+               {
+                       switch(itemsize)
+                       {
+                               case "mega": e.monster_loot = spawnfunc_item_armor_large; break;
+                               case "large": e.monster_loot = spawnfunc_item_armor_big; break;
+                               case "medium": e.monster_loot = spawnfunc_item_armor_medium; break;
+                               case "small": e.monster_loot = spawnfunc_item_armor_small; break;
+                       }
+                       break;
+               }
+               case "health":
+               {
+                       switch(itemsize)
+                       {
+                               case "mega": e.monster_loot = spawnfunc_item_health_mega; break;
+                               case "large": e.monster_loot = spawnfunc_item_health_large; break;
+                               case "medium": e.monster_loot = spawnfunc_item_health_medium; break;
+                               case "small": e.monster_loot = spawnfunc_item_health_small; break;
+                       }
+                       break;
+               }
+               case "ammo":
+               {
+                       switch(itemsize)
+                       {
+                               case "shells": e.monster_loot = spawnfunc_item_shells; break;
+                               case "cells": e.monster_loot = spawnfunc_item_cells; break;
+                               case "rockets": e.monster_loot = spawnfunc_item_rockets; break;
+                               case "bullets":
+                               case "nails": e.monster_loot = spawnfunc_item_bullets; break;
+                       }
+                       break;
+               }
+       }
+       
+       other = e;
+       MUTATOR_CALLHOOK(MonsterDropItem);
+       e = other;
+               
+       e.think = monster_item_spawn;
+       e.nextthink = time + 0.1;
+}
+
+void monsters_setframe(float _frame)
+{
+       if(self.frame == _frame)
+               return;
+               
+       self.anim_start_time = time;
+       self.frame = _frame;
+       self.SendFlags |= MSF_ANIM;
+}
+
+float monster_isvalidtarget (entity targ, entity ent)
+{
+       if(!targ || !ent)
+               return FALSE; // someone doesn't exist
+               
+       if(time < game_starttime)
+               return FALSE; // monsters do nothing before the match has started
+               
+       WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent);
+       
+       if(vlen(targ.origin - ent.origin) >= ent.target_range)
+               return FALSE; // enemy is too far away
+               
+       if not(targ.vehicle_flags & VHF_ISVEHICLE)
+       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(substring(targ.classname, 0, 10) == "onslaught_")
+               return FALSE; // don't attack onslaught targets
+       
+       if(IS_SPEC(targ) || IS_OBSERVER(targ))
+               return FALSE; // enemy is a spectator
+       
+       if not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive?
+       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 not(targ.vehicle_flags & VHF_ISVEHICLE)
+       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
+               
+       if(autocvar_g_monsters_target_infront)
+       if(ent.enemy != targ)
+       {
+               float dot;
+
+               makevectors (ent.angles);
+               dot = normalize (targ.origin - ent.origin) * v_forward;
+               
+               if(dot <= 0.3)
+                       return FALSE;
+       }
+       
+       return TRUE;
+}
+
+entity FindTarget (entity ent) 
+{
+       if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
+       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, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
+
+       self.msound_delay = time + sound_delay;
+}
+
+void monster_precachesounds(entity e)
+{
+       precache_sound(e.msound_idle);
+       precache_sound(e.msound_death);
+       precache_sound(e.msound_attack_melee);
+       precache_sound(e.msound_attack_ranged);
+       precache_sound(e.msound_sight);
+       precache_sound(e.msound_pain);
+}
+
+void monster_setupsounds(string mon)
+{
+       if(self.msound_idle == "") self.msound_idle = strzone(strcat("monsters/", mon, "_idle.wav"));
+       if(self.msound_death == "") self.msound_death = strzone(strcat("monsters/", mon, "_death.wav"));
+       if(self.msound_pain == "") self.msound_pain = strzone(strcat("monsters/", mon, "_pain.wav"));
+       if(self.msound_attack_melee == "") self.msound_attack_melee = strzone(strcat("monsters/", mon, "_melee.wav"));
+       if(self.msound_attack_ranged == "") self.msound_attack_ranged = strzone(strcat("monsters/", mon, "_attack.wav"));
+       if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav"));
+}
+
+float monster_melee (entity targ, float damg, float er, float deathtype, float dostop)
+{
+       float dot, rdmg = damg * random();
+
+       if (self.health <= 0)
+               return FALSE;
+       if (targ == world)
+               return FALSE;
+               
+       if(dostop)
+       {
+               self.velocity_x = 0;
+               self.velocity_y = 0;
+               self.state = MONSTER_STATE_ATTACK_MELEE;
+               self.SendFlags |= MSF_MOVE;
+       }
+
+       makevectors (self.angles);
+       dot = normalize (targ.origin - self.origin) * v_forward;
+       
+       if(dot > er)
+               Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
+               
+       return TRUE;
+}
+
+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"));
+       
+       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 Monster_CheckMinibossFlag ()
+{
+       if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
+               return;
+               
+       float 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;
+               self.flags |= MONSTERFLAG_MINIBOSS;
+               if not(self.weapon)
+                       self.weapon = WEP_NEX;
+       }
+}
+
+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;
+               self.think = self.monster_spawnfunc;
+               self.nextthink = time + self.respawntime;
+               self.deadflag = DEAD_RESPAWNING;
+               if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
+               {
+                       self.pos1 = self.origin;
+                       self.pos2 = self.angles;
+               }
+               self.event_damage = func_null;
+               self.takedamage = DAMAGE_NO;
+               setorigin(self, self.pos1);
+               self.angles = self.pos2;
+               self.health = self.max_health; // TODO: check if resetting to max_health is wise here
+               
+               self.SendFlags |= MSF_MOVE;
+               self.SendFlags |= MSF_STATUS;
+               
+               return;
+       }
+       SUB_SetFade(self, time + 3, 1);
+}
+
+float Monster_CanJump (vector vel)
+{
+       if(self.state)
+               return FALSE; // already attacking
+       if not(self.flags & FL_ONGROUND)
+               return FALSE; // not on the ground
+       if(self.health <= 0)
+               return FALSE; // called when dead?
+       if(time < self.attack_finished_single)
+               return FALSE; // still attacking
+
+       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(!Monster_CanJump(vel))
+               return FALSE;
+               
+       monsters_setframe(anm);
+       self.state = MONSTER_STATE_ATTACK_LEAP;
+       self.touch = touchfunc;
+       self.origin_z += 1;
+       self.velocity = vel;
+       self.flags &~= FL_ONGROUND;
+               
+       self.attack_finished_single = time + anim_finished;
+       
+       return TRUE;
+}
+
+void monster_checkattack(entity e, entity targ)
+{
+       if(e == world)
+               return;
+       if(targ == world)
+               return;
+               
+       if not(e.monster_attackfunc)
+               return;
+       
+       if(time < e.attack_finished_single)
+               return;
+               
+       if(vlen(targ.origin - e.origin) <= e.attack_range)
+       if(e.monster_attackfunc(MONSTER_ATTACK_MELEE))
+       {
+               monster_sound(e.msound_attack_melee, 0, FALSE);
+               return;
+       }
+       
+       if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
+       {
+               monster_sound(e.msound_attack_ranged, 0, FALSE);
+               return;
+       }
+}
+
+void monster_makevectors(entity e)
+{
+       vector v;
+               
+       v = CENTER_OR_VIEWOFS(e);
+       self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+       self.v_angle_x = -self.v_angle_x;
+       
+       makevectors(self.v_angle);
+}
+
+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);
+}
+
+.float last_trace;
+.float last_enemycheck; // for checking enemy
+vector monster_pickmovetarget(entity targ)
+{
+       // enemy is always preferred target
+       if(self.enemy)
+       {
+               self.monster_movestate = MONSTER_MOVE_ENEMY;
+               self.last_trace = time + 1.2;
+               return self.enemy.origin;
+       }
+       
+       switch(self.monster_moveflags)
+       {
+               case MONSTER_MOVE_OWNER:
+               {
+                       self.monster_movestate = MONSTER_MOVE_OWNER;
+                       self.last_trace = time + 0.3;
+                       if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint")
+                               return self.monster_owner.origin;
+               }
+               case MONSTER_MOVE_SPAWNLOC:
+               {
+                       self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
+                       self.last_trace = time + 2;
+                       return self.pos1;
+               }
+               case MONSTER_MOVE_NOMOVE:
+               {
+                       self.monster_movestate = MONSTER_MOVE_NOMOVE;
+                       self.last_trace = time + 2;
+                       return self.origin;
+               }
+               default:
+               case MONSTER_MOVE_WANDER:
+               {
+                       vector pos;
+                       self.monster_movestate = MONSTER_MOVE_WANDER;
+                       self.last_trace = time + 2;
+                               
+                       self.angles_y = random() * 500;
+                       makevectors(self.angles);
+                       pos = self.origin + v_forward * 600;
+                       
+                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
+                       {
+                               pos_z = random() * 200;
+                               if(random() >= 0.5)
+                                       pos_z *= -1;
+                       }
+                       
+                       if(targ)
+                       {
+                               self.last_trace = time + 0.5;
+                               pos = targ.origin;
+                       }
+                       
+                       return pos;
+               }
+       }
+}
+
+void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
+{
+       fixedmakevectors(self.angles);
+
+       if(self.target2)
+               self.goalentity = find(world, targetname, self.target2);
+               
+       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
+                       
+               self.SendFlags |= MSF_MOVE;
+                       
+               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;
+                               
+                               Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
+                               self.angles = '90 90 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;
+                               
+                       self.SendFlags |= MSF_MOVE | MSF_ANG;
+                       
+                       return;
+               }
+               else
+               {
+                       self.angles = '0 0 0';
+                       self.movetype = MOVETYPE_WALK;
+               }
+       }
+       
+       targ = self.goalentity;
+       
+       monster_target = targ;
+       monster_speed_run = runspeed;
+       monster_speed_walk = walkspeed;
+       
+       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+       {
+               runspeed = walkspeed = 0;
+               if(time >= self.spawn_time)
+                       monsters_setframe(manim_idle);
+               movelib_beak_simple(stopspeed);
+               self.SendFlags |= MSF_MOVE;
+               return;
+       }
+       
+       targ = monster_target;
+       runspeed = monster_speed_run;
+       walkspeed = monster_speed_walk;
+               
+       if(IsDifferentTeam(self.monster_owner, self))
+               self.monster_owner = world;
+               
+       if(time >= self.last_enemycheck)
+       {
+               if not(monster_isvalidtarget(self.enemy, self))
+                       self.enemy = world;
+               self.last_enemycheck = time + 2;
+       }
+               
+       if(self.enemy && self.enemy.health < 1)
+               self.enemy = world; // enough!
+               
+       if not(self.enemy)
+       {
+               self.enemy = FindTarget(self);
+               if(self.enemy)
+                       monster_sound(self.msound_sight, 0, FALSE);
+       }
+       
+       if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
+               self.state = 0;
+               
+       if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set
+       if(time >= self.last_trace || self.enemy) // update enemy instantly
+               self.moveto = monster_pickmovetarget(targ);
+
+       if not(self.enemy)
+               monster_sound(self.msound_idle, 5, TRUE);
+       
+       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+               self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
+       {
+               self.state = 0;
+               self.touch = MonsterTouch;
+       }
+       
+       //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       float turny = 0;
+       vector real_angle = vectoangles(self.steerto) - self.angles;
+       
+       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+               turny = 20;
+               
+       if(g_td || self.flags & FL_SWIM)
+               turny = vlen(self.angles - self.moveto);
+       
+       if(turny)
+       {
+               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+               self.angles_y += turny;
+       }
+       
+       if(self.state == MONSTER_STATE_ATTACK_MELEE)
+               self.moveto = self.origin;
+       else if(self.enemy)
+               self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1;
+       
+       if not(self.flags & FL_FLY || self.flags & FL_SWIM)
+               self.moveto_z = self.origin_z; 
+       
+       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(self.flags & FL_FLY || self.flags & FL_SWIM)
+               v_forward = normalize(self.moveto - self.origin);
+       
+       if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8))
+       if(self.flags & FL_ONGROUND)
+               movelib_jump_simple(100);
+
+       if(vlen(self.origin - self.moveto) > 64)
+       {
+               if(self.flags & FL_FLY || self.flags & FL_SWIM)
+                       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)
+                       monsters_setframe((self.enemy) ? manim_run : manim_walk);
+       }
+       else
+       {
+               entity e = find(world, targetname, self.target2);
+               if(e.target2)
+                       self.target2 = e.target2;
+               else if(e.target)
+                       self.target2 = e.target;
+               
+               movelib_beak_simple(stopspeed);
+               if(time > self.attack_finished_single)
+               if(time > self.pain_finished)
+               if (vlen(self.velocity) <= 30)
+                       monsters_setframe(manim_idle);
+       }
+       
+       if(self.enemy)
+               monster_checkattack(self, self.enemy);
+               
+       self.SendFlags |= MSF_ANG;
+       self.SendFlags |= MSF_MOVE;
+}
+
+void monster_dead_think()
+{
+       self.think = monster_dead_think;
+       self.nextthink = time + 0.3; // don't need to update so often now
+       
+       self.deadflag = DEAD_DEAD;
+
+       if(time >= self.ltime)
+       {
+               Monster_Fade();
+               return;
+       }
+       
+       self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location
+}
+
+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();
+}
+
+float Monster_CheckAppearFlags(entity ent)
+{
+       if not(ent.spawnflags & MONSTERFLAG_APPEAR)
+               return FALSE;
+       
+       ent.think = func_null;
+       ent.nextthink = 0;
+       ent.use = Monster_Appear;
+       ent.flags = FL_MONSTER; // set so this monster can get butchered
+       
+       return TRUE;
+}
+
+void monsters_reset()
+{
+       setorigin(self, self.pos1);
+       self.angles = self.pos2;
+       
+       self.health = self.max_health;
+       self.velocity = '0 0 0';
+       self.enemy = world;
+       self.goalentity = world;
+       self.attack_finished_single = 0;
+       self.moveto = self.origin;
+       
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+float monster_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_MONSTER);    
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & MSF_SETUP)
+       {
+           WriteByte(MSG_ENTITY, self.monsterid);
+           
+           WriteCoord(MSG_ENTITY, self.origin_x);
+           WriteCoord(MSG_ENTITY, self.origin_y);
+           WriteCoord(MSG_ENTITY, self.origin_z);
+           
+           WriteAngle(MSG_ENTITY, self.angles_x);
+           WriteAngle(MSG_ENTITY, self.angles_y);
+               
+               WriteByte(MSG_ENTITY, self.skin);
+               WriteByte(MSG_ENTITY, self.team);
+    }
+    
+    if(sf & MSF_ANG)
+    {
+        WriteShort(MSG_ENTITY, rint(self.angles_x));
+        WriteShort(MSG_ENTITY, rint(self.angles_y));
+    }
+    
+    if(sf & MSF_MOVE)
+    {
+        WriteShort(MSG_ENTITY, rint(self.origin_x));
+        WriteShort(MSG_ENTITY, rint(self.origin_y));
+        WriteShort(MSG_ENTITY, rint(self.origin_z));
+
+        WriteShort(MSG_ENTITY, rint(self.velocity_x));
+        WriteShort(MSG_ENTITY, rint(self.velocity_y));
+        WriteShort(MSG_ENTITY, rint(self.velocity_z));        
+        
+        WriteShort(MSG_ENTITY, rint(self.angles_y));        
+    }
+    
+    if(sf & MSF_ANIM)
+    {
+        WriteCoord(MSG_ENTITY, self.anim_start_time);
+        WriteByte(MSG_ENTITY, self.frame);
+    }
+    
+    if(sf & MSF_STATUS)
+    {
+               WriteByte(MSG_ENTITY, self.skin);
+               
+        WriteByte(MSG_ENTITY, self.team);
+               
+               WriteByte(MSG_ENTITY, self.deadflag);
+        
+        if(self.health <= 0)
+            WriteByte(MSG_ENTITY, 0);
+        else
+            WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
+    }
+    
+       return TRUE;
+}
+
+void monster_link(void() spawnproc)
+{
+    Net_LinkEntity(self, TRUE, 0, monster_send);
+    self.think      = spawnproc;
+    self.nextthink  = time;
+}
+
+void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       self.health -= damage;
+               
+       Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+               
+       if(self.health <= -100) // 100 health until gone?
+       {
+               Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+               
+               self.think = SUB_Remove;
+               self.nextthink = time + 0.1;
+       }
+}
+
+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(time < self.spawnshieldtime)
+               return;
+               
+       if((ignore_turrets && !(attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)) || !ignore_turrets)
+       if(monster_isvalidtarget(attacker, self))
+               self.enemy = attacker;
+               
+       if(deathtype != DEATH_KILL)
+               damage *= self.armorvalue;
+               
+       if(self.weaponentity && self.weaponentity.classname == "shield")
+               self.weaponentity.health -= damage;
+               
+       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
+                       
+               // TODO: fix this?
+               activator = attacker;
+               other = self.enemy;
+               SUB_UseTargets();
+               self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
+       
+               self.monster_die();
+               
+               frag_attacker = attacker;
+               frag_target = self;
+               MUTATOR_CALLHOOK(MonsterDies);
+               
+               if(self.health <= -100) // check if we're already gibbed
+               {
+                       Violence_GibSplash(self, 1, 0.5, attacker);
+               
+                       self.think = SUB_Remove;
+                       self.nextthink = time + 0.1;
+               }
+       }
+       
+       self.SendFlags |= MSF_STATUS;
+}
+
+// used to hook into monster post death functions without a mutator
+void monster_hook_death()
+{
+       WaypointSprite_Kill(self.sprite);
+               
+       if(self.weaponentity)
+       {
+               remove(self.weaponentity);
+               self.weaponentity = world;
+       }
+               
+       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(IS_CLIENT(self.realowner))
+               self.realowner.monstercount -= 1;
+               
+       self.event_damage       = monsters_corpse_damage;
+       self.solid                      = SOLID_CORPSE;
+       self.takedamage         = DAMAGE_AIM;
+       self.enemy                      = world;
+       self.movetype           = MOVETYPE_TOSS;
+       self.moveto                     = self.origin;
+       self.touch                      = MonsterTouch; // reset incase monster was pouncing
+       
+       if not(self.flags & FL_FLY)
+               self.velocity = '0 0 0';
+       
+       self.SendFlags |= MSF_MOVE;
+               
+       totalspawned -= 1;
+}
+
+// used to hook into monster post spawn functions without a mutator
+void monster_hook_spawn()
+{
+       if not(self.monster_respawned)
+               Monster_CheckMinibossFlag();
+       
+       self.max_health = self.health;
+       self.pain_finished = self.nextthink;
+       self.anim_start_time = time;
+       
+       if not(self.noalign)
+       {
+               setorigin(self, self.origin + '0 0 20');
+               tracebox(self.origin + '0 0 100', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
+               setorigin(self, trace_endpos);
+       }
+       
+       if not(self.monster_respawned)
+       if not(self.skin)
+               self.skin = rint(random() * 4);
+       
+       self.pos1 = self.origin;
+
+       monster_precachesounds(self);
+       
+       if(teamplay)
+               self.monster_attack = TRUE; // we can have monster enemies in team games
+               
+       if(autocvar_g_monsters_healthbars)
+       {
+               WaypointSprite_Spawn(strzone(strdecolorize(self.netname)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), 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);
+       
+       self.SendFlags = MSF_SETUP;
+}
+
+float monster_initialize(string  net_name, float mon_id,
+                                                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.netname == "")
+               self.netname = ((net_name == "") ? self.classname : net_name);
+       
+       if(self.team && !teamplay)
+               self.team = 0;
+
+       self.flags = FL_MONSTER;
+               
+       if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
+       if not(self.monster_respawned)
+               monsters_total += 1;
+
+       setsize(self, min_s, max_s);
+       self.takedamage                 = DAMAGE_AIM;
+       self.bot_attack                 = TRUE;
+       self.iscreature                 = TRUE;
+       self.teleportable               = TRUE;
+       self.damagedbycontents  = TRUE;
+       self.monsterid                  = mon_id;
+       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.scale                              = 1;
+       self.movetype                   = MOVETYPE_WALK;
+       self.spawnshieldtime    = time + autocvar_g_monsters_spawnshieldtime;
+       monsters_spawned           += 1;
+       self.enemy                              = world;
+       self.velocity                   = '0 0 0';
+       self.moveto                             = self.origin;
+       self.pos2                               = self.angles;
+       self.reset                              = monsters_reset;
+       self.candrop                    = TRUE;
+       self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
+       self.oldtarget2                 = self.target2;
+       self.deadflag                   = DEAD_NO; // UNDEAD
+       self.noalign                    = nodrop;
+       self.spawn_time                 = time;
+       self.gravity                    = 1;
+       self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+       
+       if not(self.attack_range)
+               self.attack_range = 120;
+       
+       if not(self.ticrate)
+               self.ticrate = autocvar_g_monsters_think_delay;
+               
+       self.ticrate = bound(sys_frametime, self.ticrate, 60);
+       
+       if not(self.armorvalue)
+               self.armorvalue = 1; // multiplier
+       
+       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;
+       
+       monster_link(spawnproc);
+
+       return TRUE;
+}
diff --git a/qcsrc/server/monsters/lib/monsters_early.qh b/qcsrc/server/monsters/lib/monsters_early.qh
new file mode 100644 (file)
index 0000000..445b3d0
--- /dev/null
@@ -0,0 +1,90 @@
+// for definitions used outside the monsters folder
+
+#ifdef SVQC
+.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
+#endif // SVQC
+
+#ifndef MENUQC
+.float monsterid;
+// Monster IDs
+float MONSTER_FIRST            = 1;
+float MONSTER_ZOMBIE           = 2;
+float MONSTER_BRUTE            = 3;
+float MONSTER_ANIMUS           = 4;
+float MONSTER_SHAMBLER                 = 5;
+float MONSTER_BRUISER          = 6;
+float MONSTER_WYVERN           = 7;
+float MONSTER_CERBERUS         = 8;
+float MONSTER_SLIME            = 9;
+float MONSTER_KNIGHT           = 10;
+float MONSTER_STINGRAY                 = 11;
+float MONSTER_MAGE                     = 12;
+float MONSTER_SPIDER           = 13;
+float MONSTER_LAST                     = 14;
+
+// id-string converters (TODO: remove these!)
+string monster_id2string(float mnster)
+{
+       switch(mnster)
+       {
+               case MONSTER_ZOMBIE:    return "zombie";
+               case MONSTER_BRUTE:     return "brute";
+               case MONSTER_ANIMUS:    return "animus";
+               case MONSTER_SHAMBLER:  return "shambler";
+               case MONSTER_BRUISER:   return "bruiser";
+               case MONSTER_WYVERN:    return "wyvern";
+               case MONSTER_CERBERUS:  return "cerberus";
+               case MONSTER_SLIME:     return "slime";
+               case MONSTER_KNIGHT:    return "knight";
+               case MONSTER_STINGRAY:  return "stingray";
+               case MONSTER_MAGE:              return "mage";
+               case MONSTER_SPIDER:    return "spider";
+               default: return "";
+       }
+}
+float monster_string2id(string monster)
+{
+       switch(monster)
+       {
+               case "zombie":          return MONSTER_ZOMBIE;
+               case "brute":           return MONSTER_BRUTE;
+               case "animus":          return MONSTER_ANIMUS;
+               case "shambler":        return MONSTER_SHAMBLER;
+               case "bruiser":         return MONSTER_BRUISER;
+               case "wyvern":          return MONSTER_WYVERN;
+               case "cerberus":        return MONSTER_CERBERUS;
+               case "slime":           return MONSTER_SLIME;
+               case "knight":          return MONSTER_KNIGHT;
+               case "stingray":        return MONSTER_STINGRAY;
+               case "mage":            return MONSTER_MAGE;
+               case "spider":          return MONSTER_SPIDER;
+               default:                        return 0;
+       }
+}
+
+.float anim_start_time;
+
+float MSF_UPDATE       = 2;
+float MSF_STATUS       = 4;
+float MSF_SETUP        = 8;
+float MSF_ANG          = 16;
+float MSF_MOVE         = 32;
+float MSF_ANIM         = 64;
+
+float MSF_FULL_UPDATE  = 16777215;
+
+#endif // CSQC/SVQC
\ No newline at end of file
diff --git a/qcsrc/server/monsters/lib/spawn.qc b/qcsrc/server/monsters/lib/spawn.qc
new file mode 100644 (file)
index 0000000..b985c7f
--- /dev/null
@@ -0,0 +1,57 @@
+entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+{
+       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(monster != "")
+       if not(monster_string2id(monster))
+               monster = "bruiser";
+               
+       if(monster == "")
+       if(mnster)
+               monster = monster_id2string(mnster);
+       
+       e.realowner = spawnedby;
+       
+       if(moveflag)
+               e.monster_moveflags = moveflag;
+       
+       if (spawnedby.classname == "td_spawnpoint")
+       {
+               e.monster_owner = own;
+               e.team = spawnedby.team;
+       }
+       else if(IS_PLAYER(spawnedby))
+       {
+               if(teamplay && autocvar_g_monsters_teams)
+                       e.team = spawnedby.team; // colors handled in spawn code
+                       
+               if(e.team)
+                       e.colormap = 1024;
+               else
+                       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;
+       }
+               
+       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/animus.qc b/qcsrc/server/monsters/monster/animus.qc
new file mode 100644 (file)
index 0000000..cfb2292
--- /dev/null
@@ -0,0 +1,127 @@
+const vector ANIMUS_MIN = '-41 -41 -31';
+const vector ANIMUS_MAX = '41 41 31';
+
+string ANIMUS_MODEL = "models/monsters/demon.mdl";
+
+#ifdef SVQC
+float autocvar_g_monster_animus;
+float autocvar_g_monster_animus_health;
+float autocvar_g_monster_animus_attack_jump_damage;
+float autocvar_g_monster_animus_damage;
+float autocvar_g_monster_animus_speed_walk;
+float autocvar_g_monster_animus_speed_run;
+
+const float animus_anim_stand  = 0;
+const float animus_anim_walk   = 1;
+const float animus_anim_run            = 2;
+const float animus_anim_leap   = 3;
+const float animus_anim_pain   = 4;
+const float animus_anim_death  = 5;
+const float animus_anim_attack = 6;
+
+void animus_think()
+{
+       self.think = animus_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_animus_speed_run, autocvar_g_monster_animus_speed_walk, 100, animus_anim_run, animus_anim_walk, animus_anim_stand);
+}
+
+void animus_touch_jump()
+{
+       if (self.health <= 0)
+               return;
+
+       if (monster_isvalidtarget(other, self))
+       {
+               if (vlen(self.velocity) > 300)
+               {
+                       Damage(other, self, self, autocvar_g_monster_animus_attack_jump_damage * monster_skill, DEATH_MONSTER_ANIMUS, other.origin, normalize(other.origin - self.origin));
+                       self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+               }
+       }
+
+       if(trace_dphitcontents)
+               self.touch = MonsterTouch;
+}
+
+float animus_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(animus_anim_attack);
+                       self.attack_finished_single = time + 1;
+                       monster_melee(self.enemy, autocvar_g_monster_animus_damage, 0.3, DEATH_MONSTER_ANIMUS, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(monster_leap(animus_anim_leap, animus_touch_jump, v_forward * 700 + '0 0 300', 0.8))
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void animus_die()
+{
+       Monster_CheckDropCvars ("animus");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(animus_anim_death);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void animus_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_animus_health;
+
+       self.damageforcescale   = 0;
+       self.classname                  = "monster_animus";
+       self.monster_attackfunc = animus_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = animus_think;
+       
+       monsters_setframe(animus_anim_stand);
+       
+       monster_setupsounds("animus");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_animus()
+{
+       if not(autocvar_g_monster_animus) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_animus;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       self.scale = 1.3;
+       
+       if not (monster_initialize(
+                        "Animus", MONSTER_ANIMUS,
+                        ANIMUS_MIN, ANIMUS_MAX,
+                        FALSE,
+                        animus_die, animus_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_demon1() { spawnfunc_monster_animus(); }
+void spawnfunc_monster_demon() { spawnfunc_monster_animus(); }
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/bruiser.qc b/qcsrc/server/monsters/monster/bruiser.qc
new file mode 100644 (file)
index 0000000..e19066b
--- /dev/null
@@ -0,0 +1,105 @@
+const vector BRUISER_MIN = '-20 -20 -31';
+const vector BRUISER_MAX = '20 20 53';
+
+string BRUISER_MODEL = "models/monsters/knight.mdl";
+
+#ifdef SVQC
+float autocvar_g_monster_bruiser;
+float autocvar_g_monster_bruiser_health;
+float autocvar_g_monster_bruiser_melee_damage;
+float autocvar_g_monster_bruiser_speed_walk;
+float autocvar_g_monster_bruiser_speed_run;
+
+const float bruiser_anim_stand                 = 0;
+const float bruiser_anim_run           = 1;
+const float bruiser_anim_runattack     = 2;
+const float bruiser_anim_pain1                 = 3;
+const float bruiser_anim_pain2                 = 4;
+const float bruiser_anim_attack        = 5;
+const float bruiser_anim_walk          = 6;
+const float bruiser_anim_kneel                 = 7;
+const float bruiser_anim_standing      = 8;
+const float bruiser_anim_death1        = 9;
+const float bruiser_anim_death2        = 10;
+
+void bruiser_think()
+{
+       self.think = bruiser_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_bruiser_speed_run, autocvar_g_monster_bruiser_speed_walk, 50, bruiser_anim_run, bruiser_anim_walk, bruiser_anim_stand);
+}
+
+float bruiser_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float len = vlen(self.velocity);
+                       monsters_setframe((len < 50) ? bruiser_anim_attack : bruiser_anim_runattack);
+                       self.attack_finished_single = time + 1.25;
+                       
+                       monster_melee(self.enemy, autocvar_g_monster_bruiser_melee_damage, 0.3, DEATH_MONSTER_BRUISER, FALSE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+       }
+       
+       return FALSE;
+}
+
+void bruiser_die()
+{
+       Monster_CheckDropCvars ("bruiser");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe((random() > 0.5) ? bruiser_anim_death1 : bruiser_anim_death2);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void bruiser_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_bruiser_health;
+
+       self.damageforcescale   = 0.003;
+       self.classname                  = "monster_bruiser";
+       self.monster_attackfunc = bruiser_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = bruiser_think;
+       
+       monsters_setframe(bruiser_anim_stand);
+       
+       monster_setupsounds("bruiser");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_bruiser()
+{
+       if not(autocvar_g_monster_bruiser) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_bruiser;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       self.scale = 1.3;
+       
+       if not (monster_initialize(
+                        "Bruiser", MONSTER_BRUISER,
+                        BRUISER_MIN, BRUISER_MAX,
+                        FALSE,
+                        bruiser_die, bruiser_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/brute.qc b/qcsrc/server/monsters/monster/brute.qc
new file mode 100644 (file)
index 0000000..8952d54
--- /dev/null
@@ -0,0 +1,237 @@
+const vector BRUTE_MIN = '-36 -36 -20';
+const vector BRUTE_MAX = '36 36 50';
+
+string BRUTE_MODEL = "models/monsters/ogre.dpm";
+
+#ifdef SVQC
+float autocvar_g_monster_brute;
+float autocvar_g_monster_brute_health;
+float autocvar_g_monster_brute_chainsaw_damage;
+float autocvar_g_monster_brute_speed_walk;
+float autocvar_g_monster_brute_speed_run;
+float autocvar_g_monster_brute_attack_uzi_bullets;
+float autocvar_g_monster_brute_attack_uzi_damage;
+float autocvar_g_monster_brute_attack_uzi_force;
+float autocvar_g_monster_brute_attack_uzi_chance;
+float autocvar_g_monster_brute_attack_grenade_damage;
+float autocvar_g_monster_brute_attack_grenade_edgedamage;
+float autocvar_g_monster_brute_attack_grenade_force;
+float autocvar_g_monster_brute_attack_grenade_radius;
+
+const float brute_anim_idle            = 0;
+const float brute_anim_walk            = 1;
+const float brute_anim_run                     = 2;
+const float brute_anim_pain            = 3;
+const float brute_anim_swing           = 4;
+const float brute_anim_die                     = 5;
+
+.float brute_cycles;
+
+void brute_think()
+{
+       self.think = brute_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_brute_speed_run, autocvar_g_monster_brute_speed_walk, 300, brute_anim_run, brute_anim_walk, brute_anim_idle);
+}
+
+void brute_blade()
+{
+       self.brute_cycles += 1;
+       self.angles_y = self.angles_y + random()* 25;
+       
+       monster_melee(self.enemy, autocvar_g_monster_brute_chainsaw_damage, 0.3, DEATH_MONSTER_BRUTE_BLADE, TRUE);
+       
+       if(self.brute_cycles <= 4)
+               defer(0.2, brute_blade);
+}
+
+void brute_uzi()
+{
+       self.brute_cycles += 1;
+       
+       monster_makevectors(self.enemy);
+       
+       W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_uzi_damage);
+       fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_brute_attack_uzi_damage, autocvar_g_monster_brute_attack_uzi_force, DEATH_MONSTER_BRUTE_UZI, 0, 1, 115);
+       endFireBallisticBullet();
+       
+       if(self.brute_cycles <= autocvar_g_monster_brute_attack_uzi_bullets)
+               defer(0.1, brute_uzi);
+}
+
+void brute_grenade_explode()
+{
+       pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1);
+       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage (self, self.realowner, autocvar_g_monster_brute_attack_grenade_damage, autocvar_g_monster_brute_attack_grenade_edgedamage, autocvar_g_monster_brute_attack_grenade_radius, world, autocvar_g_monster_brute_attack_grenade_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void brute_grenade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+               
+       self.health = self.health - damage;
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, self.use);
+}
+
+void brute_grenade_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       self.use ();
+}
+
+void brute_grenade_think()
+{
+       self.nextthink = time;
+       if (time > self.cnt)
+       {
+               other = world;
+               brute_grenade_explode();
+               return;
+       }
+}
+
+void brute_grenade()
+{
+       entity gren;
+
+       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_grenade_damage);
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = autocvar_g_monster_brute_attack_grenade_damage;
+       gren.movetype = MOVETYPE_BOUNCE;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-3 -3 -3', '3 3 3');
+
+       gren.cnt = time + 5;
+       gren.nextthink = time;
+       gren.think = brute_grenade_think;
+       gren.use = brute_grenade_explode;
+       gren.touch = brute_grenade_touch;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = autocvar_g_balance_grenadelauncher_primary_health;
+       gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale;
+       gren.event_damage = brute_grenade_damage;
+       gren.damagedbycontents = TRUE;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary);
+
+       gren.angles = vectoangles (gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
+}
+
+float brute_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       self.brute_cycles = 0;
+                       monsters_setframe(brute_anim_swing);
+                       self.attack_finished_single = time + 1.3;
+                       brute_blade();
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       self.brute_cycles = 0;
+                       if(random() <= autocvar_g_monster_brute_attack_uzi_chance)
+                       {
+                               monsters_setframe(brute_anim_pain);
+                               self.attack_finished_single = time + 0.8;
+                               defer(0.1, brute_uzi);
+                       }
+                       else
+                       {
+                               monster_makevectors(self.enemy);
+                               brute_grenade();
+                               monsters_setframe(brute_anim_pain);
+                               self.attack_finished_single = time + 1.2;
+                       }
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void brute_die()
+{
+       Monster_CheckDropCvars ("brute");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(brute_anim_die);
+               
+       monster_hook_death(); // for post-death mods
+}
+
+void brute_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_brute_health;
+
+       self.damageforcescale   = 0.003;
+       self.classname                  = "monster_brute";
+       self.monster_attackfunc = brute_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = brute_think;
+       self.weapon                             = WEP_GRENADE_LAUNCHER;
+       
+       monsters_setframe(brute_anim_idle);
+       
+       monster_setupsounds("brute");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_brute()
+{
+       if not(autocvar_g_monster_brute) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_brute;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not (monster_initialize(
+                        "Brute", MONSTER_BRUTE,
+                        BRUTE_MIN, BRUTE_MAX,
+                        FALSE,
+                        brute_die, brute_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/cerberus.qc b/qcsrc/server/monsters/monster/cerberus.qc
new file mode 100644 (file)
index 0000000..38b57e7
--- /dev/null
@@ -0,0 +1,118 @@
+const vector CERBERUS_MIN = '-16 -16 -24';
+const vector CERBERUS_MAX = '16 16 12';
+
+string CERBERUS_MODEL = "models/monsters/dog.dpm";
+
+#ifdef SVQC
+float autocvar_g_monster_cerberus;
+float autocvar_g_monster_cerberus_health;
+float autocvar_g_monster_cerberus_bite_damage;
+float autocvar_g_monster_cerberus_attack_jump_damage;
+float autocvar_g_monster_cerberus_speed_walk;
+float autocvar_g_monster_cerberus_speed_run;
+
+const float cerberus_anim_idle = 0;
+const float cerberus_anim_walk = 1;
+const float cerberus_anim_run  = 2;
+const float cerberus_anim_attack       = 3;
+const float cerberus_anim_die  = 4;
+const float cerberus_anim_pain = 5;
+
+void cerberus_think()
+{
+       self.think = cerberus_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_cerberus_speed_run, autocvar_g_monster_cerberus_speed_walk, 50, cerberus_anim_run, cerberus_anim_walk, cerberus_anim_idle);
+}
+
+void cerberus_touch_jump()
+{
+       if (other.takedamage)
+       if (vlen(self.velocity) > 300)
+       {
+               Damage(self.enemy, self, self, autocvar_g_monster_cerberus_attack_jump_damage * monster_skill, DEATH_MONSTER_CERBERUS_JUMP, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+               self.touch = MonsterTouch;
+       }
+
+       if(trace_dphitcontents)
+               self.touch = MonsterTouch;
+}
+
+float cerberus_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(cerberus_anim_attack);
+                       self.attack_finished_single = time + 0.7;
+                       monster_melee(self.enemy, autocvar_g_monster_cerberus_bite_damage, 0.2, DEATH_MONSTER_CERBERUS_BITE, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(monster_leap(cerberus_anim_attack, cerberus_touch_jump, v_forward * 300 + '0 0 200', 0.8))
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void cerberus_die()
+{
+       Monster_CheckDropCvars ("cerberus");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(cerberus_anim_die);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void cerberus_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_cerberus_health;
+
+       self.damageforcescale   = 0;
+       self.classname                  = "monster_cerberus";
+       self.monster_attackfunc = cerberus_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = cerberus_think;
+       
+       monsters_setframe(cerberus_anim_idle);
+       
+       monster_setupsounds("cerberus");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_cerberus()
+{
+       if not(autocvar_g_monster_cerberus) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_cerberus;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not (monster_initialize(
+                        "Cerberus", MONSTER_CERBERUS,
+                        CERBERUS_MIN, CERBERUS_MAX,
+                        FALSE,
+                        cerberus_die, cerberus_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_dog() { spawnfunc_monster_cerberus(); }
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/knight.qc b/qcsrc/server/monsters/monster/knight.qc
new file mode 100644 (file)
index 0000000..816ac60
--- /dev/null
@@ -0,0 +1,319 @@
+const vector KNIGHT_MIN = '-20 -20 -32';
+const vector KNIGHT_MAX = '20 20 41';
+
+string KNIGHT_MODEL = "models/monsters/hknight.mdl";
+
+#ifdef SVQC
+float autocvar_g_monster_knight;
+float autocvar_g_monster_knight_health;
+float autocvar_g_monster_knight_melee_damage;
+float autocvar_g_monster_knight_inferno_damage;
+float autocvar_g_monster_knight_inferno_damagetime;
+float autocvar_g_monster_knight_inferno_chance;
+float autocvar_g_monster_knight_speed_walk;
+float autocvar_g_monster_knight_speed_run;
+float autocvar_g_monster_knight_fireball_damage;
+float autocvar_g_monster_knight_fireball_force;
+float autocvar_g_monster_knight_fireball_radius;
+float autocvar_g_monster_knight_fireball_chance;
+float autocvar_g_monster_knight_fireball_edgedamage;
+float autocvar_g_monster_knight_spike_chance;
+float autocvar_g_monster_knight_spike_force;
+float autocvar_g_monster_knight_spike_radius;
+float autocvar_g_monster_knight_spike_edgedamage;
+float autocvar_g_monster_knight_spike_damage;
+float autocvar_g_monster_knight_jump_chance;
+float autocvar_g_monster_knight_jump_damage;
+float autocvar_g_monster_knight_jump_dist;
+
+const float knight_anim_stand  = 0;
+const float knight_anim_walk   = 1;
+const float knight_anim_run    = 2;
+const float knight_anim_pain   = 3;
+const float knight_anim_death1         = 4;
+const float knight_anim_death2         = 5;
+const float knight_anim_charge1 = 6;
+const float knight_anim_magic1         = 7;
+const float knight_anim_magic2         = 8;
+const float knight_anim_charge2 = 9;
+const float knight_anim_slice  = 10;
+const float knight_anim_smash  = 11;
+const float knight_anim_wattack = 12;
+const float knight_anim_magic3         = 13;
+
+.float knight_cycles;
+
+void knight_think()
+{
+       self.think = knight_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 100, knight_anim_run, knight_anim_walk, knight_anim_stand);
+}
+
+void knight_inferno()
+{
+       if not(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);
+       
+       if(vlen(self.enemy.origin - self.origin) <= 2000)
+               Fire_AddDamage(self.enemy, self, autocvar_g_monster_knight_inferno_damage * monster_skill, autocvar_g_monster_knight_inferno_damagetime, DEATH_MONSTER_KNIGHT_INFERNO);
+}
+
+void knight_fireball_explode()
+{
+       entity e;
+       if(self)
+       {
+               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage(self, self.realowner, autocvar_g_monster_knight_fireball_damage, autocvar_g_monster_knight_fireball_edgedamage, autocvar_g_monster_knight_fireball_force, world, autocvar_g_monster_knight_fireball_radius, self.projectiledeathtype, world);
+               
+               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_knight_fireball_radius)
+                       Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_knight_inferno_damagetime, self.projectiledeathtype);
+               
+               remove(self);
+       }
+}
+
+void knight_fireball_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       knight_fireball_explode();
+}
+
+void knight_fireball()
+{
+       entity missile = spawn();
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       
+       monster_makevectors(self.enemy);
+       
+       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;
+       missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL;
+       setsize(missile, '-6 -6 -6', '6 6 6');          
+       setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
+       missile.flags = FL_PROJECTILE;
+       missile.velocity = dir * 400;
+       missile.avelocity = '300 300 300';
+       missile.nextthink = time + 5;
+       missile.think = knight_fireball_explode;
+       missile.enemy = self.enemy;
+       missile.touch = knight_fireball_touch;
+       CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
+}
+
+void knight_spike_explode()
+{
+       if(self)
+       {
+               pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage (self, self.realowner, autocvar_g_monster_knight_spike_damage, autocvar_g_monster_knight_spike_edgedamage, autocvar_g_monster_knight_spike_force, world, autocvar_g_monster_knight_spike_radius, DEATH_MONSTER_KNIGHT_SPIKE, other);
+               remove(self);
+       }
+}
+
+void knight_spike_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       knight_spike_explode();
+}
+
+void knight_spike()
+{
+       entity missile;
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+       self.effects |= EF_MUZZLEFLASH;
+
+       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 = knight_spike_explode;
+       missile.enemy = self.enemy;
+       missile.touch = knight_spike_touch;
+       CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void knight_spikes()
+{
+       self.knight_cycles += 1;
+       knight_spike();
+       
+       if(self.knight_cycles <= 7)
+               defer(0.1, knight_spikes);
+}
+
+float knight_attack_ranged()
+{
+       if not(self.flags & FL_ONGROUND)
+               return FALSE;
+               
+       self.knight_cycles = 0;
+       
+       RandomSelection_Init();
+       RandomSelection_Add(world, 1, "", autocvar_g_monster_knight_fireball_chance, 1);
+       RandomSelection_Add(world, 2, "", autocvar_g_monster_knight_inferno_chance, 1);
+       RandomSelection_Add(world, 3, "", autocvar_g_monster_knight_spike_chance, 1);
+       if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_knight_jump_dist) ? 1 : autocvar_g_monster_knight_jump_chance), 1);
+       
+       switch(RandomSelection_chosen_float)
+       {
+               case 1:
+               {
+                       monsters_setframe(knight_anim_magic2);
+                       self.attack_finished_single = time + 2;
+                       defer(0.4, knight_fireball);
+                       
+                       return TRUE;
+               }
+               case 2:
+               {
+                       self.attack_finished_single = time + 3;
+                       defer(0.5, knight_inferno);
+                       return TRUE;
+               }
+               case 3:
+               {
+                       monsters_setframe(knight_anim_magic3);
+                       self.attack_finished_single = time + 3;
+                       defer(0.4, knight_spikes);
+                       
+                       return TRUE;
+               }
+               case 4:
+               {
+                       float er = vlen(self.enemy.origin - self.origin);
+                       
+                       if(er >= 400 && er < 1200)
+                       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_knight_jump_damage * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+                               self.attack_finished_single = time + 2;
+                               return TRUE;
+                       }
+                       return FALSE;
+               }
+       }
+       
+       return FALSE;
+}
+
+float knight_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float anim;
+                       if(random() < 0.3)
+                               anim = knight_anim_slice;
+                       else if(random() < 0.6)
+                               anim = knight_anim_smash;
+                       else
+                               anim = knight_anim_wattack;
+                       
+                       monsters_setframe(anim);
+                       self.attack_finished_single = time + 0.7;
+                       monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 0.3, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(knight_attack_ranged())
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void knight_die()
+{
+       float chance = random();
+       Monster_CheckDropCvars ("knight");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
+       
+       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;
+       }
+               
+       monster_hook_death(); // for post-death mods
+}
+
+void knight_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_knight_health;
+
+       self.damageforcescale   = 0.003;
+       self.classname                  = "monster_knight";
+       self.monster_attackfunc = knight_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = knight_think;
+       
+       monsters_setframe(knight_anim_stand);
+       
+       monster_setupsounds("knight");
+       
+       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(Monster_CheckAppearFlags(self))
+               return;
+       
+       self.scale = 1.3;
+       
+       if not (monster_initialize(
+                        "Knight", MONSTER_KNIGHT,
+                        KNIGHT_MIN, KNIGHT_MAX,
+                        FALSE,
+                        knight_die, knight_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); }
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/mage.qc b/qcsrc/server/monsters/monster/mage.qc
new file mode 100644 (file)
index 0000000..d30c208
--- /dev/null
@@ -0,0 +1,431 @@
+const vector MAGE_MIN = '-36 -36 -24';
+const vector MAGE_MAX = '36 36 50';
+
+string MAGE_MODEL = "models/monsters/mage.dpm";
+
+#ifdef SVQC
+float autocvar_g_monster_mage;
+float autocvar_g_monster_mage_health;
+float autocvar_g_monster_mage_speed;
+float autocvar_g_monster_mage_attack_spike_damage;
+float autocvar_g_monster_mage_attack_spike_radius;
+float autocvar_g_monster_mage_attack_spike_delay;
+float autocvar_g_monster_mage_attack_melee_damage;
+float autocvar_g_monster_mage_attack_melee_delay;
+float autocvar_g_monster_mage_heal_self;
+float autocvar_g_monster_mage_heal_friends;
+float autocvar_g_monster_mage_heal_minhealth;
+float autocvar_g_monster_mage_heal_range;
+float autocvar_g_monster_mage_heal_delay;
+float autocvar_g_monster_mage_shield_time;
+float autocvar_g_monster_mage_shield_delay;
+float autocvar_g_monster_mage_shield_blockpercent;
+float autocvar_g_monster_mage_attack_grenade_damage;
+float autocvar_g_monster_mage_attack_grenade_edgedamage;
+float autocvar_g_monster_mage_attack_grenade_radius;
+float autocvar_g_monster_mage_attack_grenade_lifetime;
+float autocvar_g_monster_mage_attack_grenade_force;
+float autocvar_g_monster_mage_attack_grenade_chance;
+
+const float mage_anim_idle             = 0;
+const float mage_anim_walk             = 1;
+const float mage_anim_attack   = 2;
+const float mage_anim_pain             = 3;
+const float mage_anim_death    = 4;
+const float mage_anim_run              = 5;
+
+void() mage_heal;
+void() mage_shield;
+void() mage_shield_die;
+
+float friend_needshelp(entity e)
+{
+       if(e == world)
+               return FALSE;
+       if(e.health <= 0)
+               return FALSE;
+       if(vlen(e.origin - self.origin) > autocvar_g_monster_mage_heal_range)
+               return FALSE;
+       if(IsDifferentTeam(e, self))
+               return FALSE;
+       if(e.frozen)
+               return FALSE;
+       if(!IS_PLAYER(e))
+               return (e.health < e.max_health);
+       if(e.items & IT_INVINCIBLE)
+               return FALSE;
+
+       switch(self.skin)
+       {
+               case 0:
+               {
+                       if(e.health < autocvar_g_balance_health_regenstable)
+                               return TRUE;
+                       break;
+               }
+               case 1:
+               {
+                       if((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max))
+                               return TRUE;
+                       break;
+               }
+               case 2:
+               {
+                       if(e.armorvalue < autocvar_g_balance_armor_regenstable)
+                               return TRUE;
+                       break;
+               }
+               case 3:
+               {
+                       if(e.health > 0)
+                               return TRUE;
+                       break;
+               }
+       }
+       
+       return FALSE;
+}
+
+void mage_think()
+{
+       entity head;
+       float need_help = FALSE;
+       
+       FOR_EACH_PLAYER(head)
+       if(friend_needshelp(head))
+       {
+               need_help = TRUE;
+               break; // found 1 player near us who is low on health
+       }
+       if(!need_help)
+       FOR_EACH_MONSTER(head)
+       if(head != self)
+       if(friend_needshelp(head))
+       {
+               need_help = TRUE;
+               break; // found 1 player near us who is low on health
+       }
+       
+       self.think = mage_think;
+       self.nextthink = time + self.ticrate;
+       
+       if(self.weaponentity)
+       if(time >= self.weaponentity.ltime)
+               mage_shield_die();
+               
+       if(self.health < autocvar_g_monster_mage_heal_minhealth || need_help)
+       if(time >= self.attack_finished_single)
+       if(random() < 0.5)
+               mage_heal();
+               
+       if(self.enemy)
+       if(self.health < self.max_health)
+       if(time >= self.lastshielded)
+       if(random() < 0.5)
+               mage_shield();
+       
+       monster_move(autocvar_g_monster_mage_speed, autocvar_g_monster_mage_speed, 50, mage_anim_walk, mage_anim_run, mage_anim_idle);
+}
+
+void mageattack_melee()
+{
+       monster_melee(self.enemy, autocvar_g_monster_mage_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE);
+}
+
+void mage_grenade_explode()
+{
+       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+       
+       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+       RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_grenade_damage, autocvar_g_monster_mage_attack_grenade_edgedamage, autocvar_g_monster_mage_attack_grenade_radius, world, autocvar_g_monster_mage_attack_grenade_force, DEATH_MONSTER_MAGE, other);
+       remove(self);
+}
+
+void mage_grenade_touch()
+{
+       if(IS_PLAYER(other))
+       {
+               PROJECTILE_TOUCH;
+               mage_grenade_explode();
+               return;
+       }
+}
+
+void mage_throw_itemgrenade()
+{
+       makevectors(self.angles);
+
+       W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_mage_attack_grenade_damage);
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       entity gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = FALSE;
+       gren.movetype = MOVETYPE_BOUNCE;
+       gren.solid = SOLID_TRIGGER;
+       gren.projectiledeathtype = DEATH_MONSTER_MAGE;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-64 -64 -64', '64 64 64');
+
+       gren.nextthink = time + autocvar_g_monster_mage_attack_grenade_lifetime;
+       gren.think = mage_grenade_explode;
+       gren.use = mage_grenade_explode;
+       gren.touch = mage_grenade_touch;
+
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_mage_attack_grenade);
+       
+       gren.flags = FL_PROJECTILE;
+       
+       setmodel(gren, "models/items/g_h50.md3");
+       
+       self.attack_finished_single = time + 1.5;
+}
+
+void mage_spike_explode()
+{
+       self.event_damage = func_null;
+
+       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+       RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_spike_damage, autocvar_g_monster_mage_attack_spike_damage * 0.5, autocvar_g_monster_mage_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other);
+
+       remove (self);
+}
+
+void mage_spike_touch()
+{
+       PROJECTILE_TOUCH;
+
+       mage_spike_explode();
+}
+
+void mage_spike_think()
+{
+       if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
+       {
+               mage_spike_explode();
+               return;
+       }
+       
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       
+       UpdateCSQCProjectile(self);
+       
+       if (monster_skill == 3)
+               self.velocity = dir * 350;
+       else
+               self.velocity = dir * 250;
+               
+       self.nextthink = time + 0.2;
+       self.think = mage_spike_think;  
+}
+
+void mage_spike()
+{
+       entity missile;
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+       makevectors(self.angles);
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.think = mage_spike_think;
+       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 = mage_spike_touch;
+       
+       CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
+}
+
+void mage_heal()
+{
+       entity head;
+       float washealed = FALSE;
+       
+       for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head))
+       {
+               washealed = TRUE;
+               string fx = "";
+               if(IS_PLAYER(head))
+               {
+                       switch(self.skin)
+                       {
+                               case 0:
+                                       if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_health_regenstable);
+                                       fx = "healing_fx";
+                                       break;
+                               case 1:
+                                       if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
+                                       if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
+                                       if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
+                                       if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
+                                       fx = "ammoregen_fx";
+                                       break;
+                               case 2:
+                                       if(head.armorvalue < autocvar_g_balance_armor_regenstable)
+                                       {
+                                               head.armorvalue = bound(0, head.armorvalue + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_armor_regenstable);
+                                               fx = "armorrepair_fx";
+                                       }
+                                       break;
+                               case 3:
+                                       head.health = bound(0, head.health - ((head == self)  ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_friends), autocvar_g_balance_health_regenstable);
+                                       fx = "rage";
+                                       break;
+                       }
+                       
+                       pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
+               }
+               else
+               {
+                       pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+                       head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, head.max_health);
+                       WaypointSprite_UpdateHealth(head.sprite, head.health);
+               }
+       }
+       
+       if(washealed)
+       {
+               monsters_setframe(mage_anim_attack);
+               self.attack_finished_single = time + autocvar_g_monster_mage_heal_delay;
+       }
+}
+
+void mage_shield_die()
+{
+       if not(self.weaponentity)
+               return; // why would this be called without a shield?
+       
+       self.armorvalue = 1;
+       
+       remove(self.weaponentity);
+       
+       self.weaponentity = world;
+}
+
+void mage_shield()
+{
+       if(self.weaponentity)
+               return; // already have a shield
+               
+       entity shield = spawn();
+
+       shield.owner = self;
+       shield.team = self.team;
+       shield.ltime = time + autocvar_g_monster_mage_shield_time;
+       shield.health = 70;
+       shield.classname = "shield";
+       shield.effects = EF_ADDITIVE;
+       shield.movetype = MOVETYPE_NOCLIP;
+       shield.solid = SOLID_TRIGGER;
+       shield.avelocity = '7 0 11';
+       shield.scale = self.scale * 0.6;
+       
+       setattachment(shield, self, "");
+       setmodel(shield, "models/ctf/shield.md3");
+       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+       
+       self.weaponentity = shield;
+       
+       self.lastshielded = time + autocvar_g_monster_mage_shield_delay;
+       
+       monsters_setframe(mage_anim_attack);
+       self.attack_finished_single = time + 1;
+       
+       self.armorvalue = autocvar_g_monster_mage_shield_blockpercent / 100;
+}
+
+float mage_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(mage_anim_attack);
+                       self.attack_finished_single = time + autocvar_g_monster_mage_attack_melee_delay;
+                       defer(0.2, mageattack_melee);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(random() < autocvar_g_monster_mage_attack_grenade_chance / 100)
+                       {
+                               mage_throw_itemgrenade();
+                               return TRUE;
+                       }
+       
+                       monsters_setframe(mage_anim_attack);
+                       self.attack_finished_single = time + autocvar_g_monster_mage_attack_spike_delay;
+                       defer(0.2, mage_spike);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void mage_die()
+{
+       Monster_CheckDropCvars ("mage");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(mage_anim_death);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void mage_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_mage_health;
+
+       self.damageforcescale   = 0.003;
+       self.classname                  = "monster_mage";
+       self.monster_attackfunc = mage_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = mage_think;
+       
+       monsters_setframe(mage_anim_walk);
+       
+       monster_setupsounds("mage");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_mage()
+{
+       if not(autocvar_g_monster_mage) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_mage;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not (monster_initialize(
+                        "Mage", MONSTER_MAGE,
+                        MAGE_MIN, MAGE_MAX,
+                        FALSE,
+                        mage_die, mage_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/shambler.qc b/qcsrc/server/monsters/monster/shambler.qc
new file mode 100644 (file)
index 0000000..ec78221
--- /dev/null
@@ -0,0 +1,159 @@
+const vector SHAMBLER_MIN = '-41 -41 -31';
+const vector SHAMBLER_MAX = '41 41 65';
+
+string SHAMBLER_MODEL = "models/monsters/shambler.mdl";
+
+#ifdef SVQC
+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;
+
+const float shambler_anim_stand        = 0;
+const float shambler_anim_walk                 = 1;
+const float shambler_anim_run          = 2;
+const float shambler_anim_smash        = 3;
+const float shambler_anim_swingr       = 4;
+const float shambler_anim_swingl       = 5;
+const float shambler_anim_magic        = 6;
+const float shambler_anim_pain                 = 7;
+const float shambler_anim_death        = 8;
+
+void shambler_think()
+{
+       self.think = shambler_think;
+       self.nextthink = time + self.ticrate;
+       
+       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()
+{
+       monster_melee(self.enemy, autocvar_g_monster_shambler_damage, 0.3, DEATH_MONSTER_SHAMBLER_SMASH, TRUE);
+}
+
+void shambler_delayedsmash()
+{
+       monsters_setframe(shambler_anim_smash);
+       defer(0.7, shambler_smash);
+       self.attack_finished_single = time + 1.1;
+}
+
+void shambler_swing()
+{
+       float r = (random() < 0.5);
+       monsters_setframe((r) ? shambler_anim_swingr : shambler_anim_swingl);
+       monster_melee(self.enemy, autocvar_g_monster_shambler_attack_claw_damage, 0.3, DEATH_MONSTER_SHAMBLER_CLAW, TRUE);
+       self.attack_finished_single = time + 0.8;
+       if(r)
+               defer(0.5, shambler_swing);
+}
+
+void CastLightning()
+{
+       local vector org, dir;
+       //vector v = '0 0 0';
+
+       self.effects |= EF_MUZZLEFLASH;
+
+       org = self.origin + '0 0 40';
+
+       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);
+       
+       te_csqc_lightningarc(org, trace_endpos);
+}
+
+float shambler_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float chance = random();
+
+                       if(chance > 0.6)
+                               shambler_delayedsmash();
+                       else
+                               shambler_swing();
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       monsters_setframe(shambler_anim_magic);
+                       self.attack_finished_single = time + 1.1;
+                       defer(0.6, CastLightning);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void shambler_die()
+{
+       Monster_CheckDropCvars ("shambler");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(shambler_anim_death);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void shambler_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_shambler_health;
+
+       self.damageforcescale   = 0.003;
+       self.classname                  = "monster_shambler";
+       self.monster_attackfunc = shambler_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = shambler_think;
+       self.weapon                             = WEP_NEX;
+       
+       monsters_setframe(shambler_anim_stand);
+       
+       monster_setupsounds("shambler");
+       
+       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(Monster_CheckAppearFlags(self))
+               return;
+       
+       self.scale = 1.3;
+       
+       if not (monster_initialize(
+                        "Shambler", MONSTER_SHAMBLER,
+                        SHAMBLER_MIN, SHAMBLER_MAX,
+                        FALSE,
+                        shambler_die, shambler_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/slime.qc b/qcsrc/server/monsters/monster/slime.qc
new file mode 100644 (file)
index 0000000..8f73cea
--- /dev/null
@@ -0,0 +1,141 @@
+const vector SLIME_MIN = '-16 -16 -24';
+const vector SLIME_MAX = '16 16 16';
+
+string SLIME_MODEL = "models/monsters/slime.dpm";
+
+#ifdef SVQC
+float autocvar_g_monster_slime;
+float autocvar_g_monster_slime_health;
+float autocvar_g_monster_slime_speed_walk;
+float autocvar_g_monster_slime_speed_run;
+
+const float slime_anim_walk            = 0;
+const float slime_anim_idle            = 1;
+const float slime_anim_jump            = 2;
+const float slime_anim_fly             = 3;
+const float slime_anim_die             = 4;
+const float slime_anim_pain            = 5;
+
+void slime_think()
+{
+       self.think = slime_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_slime_speed_run, autocvar_g_monster_slime_speed_walk, 20, slime_anim_walk, slime_anim_walk, slime_anim_idle);
+}
+
+void slime_touch_jump()
+{
+       if(self.health > 0)
+       if(other.health > 0)
+       if(other.takedamage)
+       if(vlen(self.velocity) > 200)
+       {
+               Damage (self, world, world, self.health + self.max_health + 200, DEATH_MONSTER_SLIME, self.origin, '0 0 0');
+                       
+               return;
+       }
+
+       if(trace_dphitcontents)
+       {
+               self.touch = MonsterTouch;
+               self.movetype = MOVETYPE_WALK;
+       }
+}
+
+float slime_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5))
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void slime_explode()
+{
+       RadiusDamage(self, self, 250 * monster_skill, 15, 250 * (monster_skill * 0.7), world, 250, DEATH_MONSTER_SLIME, world);
+       pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+       
+       setmodel(self, "");
+}
+
+void slime_dead()
+{
+       self.health = -100; // gibbed
+       slime_explode();
+       
+       Monster_CheckDropCvars ("slime"); // drop items after exploding to prevent player picking up item before dying
+       
+       self.deadflag = DEAD_DEAD;
+       self.think = Monster_Fade;
+       self.nextthink = time + 0.1;
+       
+       monster_hook_death();
+       
+       self.event_damage = func_null; // reset by monster_hook_death
+       self.takedamage = DAMAGE_NO;
+}
+
+void slime_die()
+{
+       self.think                      = slime_dead;
+       self.nextthink          = time;
+       self.event_damage   = func_null;
+       self.movetype           = MOVETYPE_NONE;
+       self.enemy                      = world;
+       self.health                     = 0;
+       
+       self.SendFlags |= MSF_MOVE | MSF_STATUS;
+}
+
+void slime_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_slime_health;
+       
+       self.damageforcescale   = 0.003;
+       self.classname                  = "monster_slime";
+       self.monster_attackfunc = slime_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = slime_think;
+       
+       monsters_setframe(slime_anim_idle);
+       
+       monster_setupsounds("slime");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_slime()
+{
+       if not(autocvar_g_monster_slime) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_slime;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not (monster_initialize(
+                        "Slime", MONSTER_SLIME,
+                        SLIME_MIN, SLIME_MAX,
+                        FALSE,
+                        slime_die, slime_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); }
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/spider.qc b/qcsrc/server/monsters/monster/spider.qc
new file mode 100644 (file)
index 0000000..cdae2f4
--- /dev/null
@@ -0,0 +1,217 @@
+const vector SPIDER_MIN = '-18 -18 -25';
+const vector SPIDER_MAX = '18 18 30';
+
+string SPIDER_MODEL = "models/monsters/spider.dpm";
+
+#ifdef SVQC
+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_attack_fire_time;
+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;
+
+const float spider_anim_idle           = 0;
+const float spider_anim_walk           = 1;
+const float spider_anim_attack         = 2;
+const float spider_anim_attack2                = 3;
+
+.float spider_type; // used to switch between fire & ice attacks
+const float SPIDER_TYPE_ICE            = 0;
+const float SPIDER_TYPE_FIRE   = 1;
+
+void spider_think()
+{
+       self.think = spider_think;
+       self.nextthink = time + self.ticrate;
+       
+       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);
+}
+
+void spider_web_explode()
+{
+       entity e;
+       if(self)
+       {
+               float damg = 0, edamg = 0, rad = 1;
+               switch(self.realowner.spider_type)
+               {
+                       case SPIDER_TYPE_ICE:
+                               rad = 25;
+                               pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
+                               break;
+                       case SPIDER_TYPE_FIRE:
+                               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+                               damg = 15;
+                               rad = 25;
+                               edamg = 6;
+                               break;
+               }
+               
+               RadiusDamage(self, self.realowner, damg, edamg, 0, world, rad, DEATH_MONSTER_SPIDER_FIRE, world); // ice deals no damage anyway
+               
+               for(e = findradius(self.origin, rad); e; e = e.chain) if(e.takedamage && e.deadflag == DEAD_NO)
+               {
+                       switch(self.realowner.spider_type)
+                       {
+                               case SPIDER_TYPE_ICE:
+                                       Freeze(e, 0.3, 2, FALSE);
+                                       break;
+                               case SPIDER_TYPE_FIRE:
+                                       Fire_AddDamage(e, self.realowner, 5 * monster_skill, autocvar_g_monster_spider_attack_fire_time, DEATH_MONSTER_SPIDER_FIRE);
+                                       break;
+                       }
+               }
+               
+               remove(self);
+       }
+}
+
+void spider_web_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       spider_web_explode();
+}
+
+void spider_shootweb(float ptype)
+{
+       float p = 0;
+       string snd = "";
+       switch(ptype)
+       {
+               case SPIDER_TYPE_ICE:
+                       p = PROJECTILE_ELECTRO;
+                       snd = "weapons/electro_fire2.wav";
+                       break;
+               case SPIDER_TYPE_FIRE:
+                       p = PROJECTILE_FIREMINE;
+                       snd = "weapons/fireball_fire.wav";
+                       break;
+       }
+       
+       vector fmins = '-4 -4 -4', fmaxs = '4 4 4';
+
+       W_SetupShot_ProjectileSize(self, fmins, fmaxs, FALSE, 2, snd, CH_WEAPON_A, 0);
+
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       entity 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 + 5;
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = DEATH_MONSTER_SPIDER_FIRE;
+       setorigin(proj, w_shotorg);
+
+       //proj.glow_size = 50;
+       //proj.glow_color = 45;
+       proj.movetype = MOVETYPE_BOUNCE;
+       W_SETUPPROJECTILEVELOCITY_UP(proj, g_monster_spider_attack_web);
+       proj.touch = spider_web_touch;
+       setsize(proj, fmins, fmaxs);
+       proj.takedamage = DAMAGE_NO;
+       proj.damageforcescale = 0;
+       proj.health = 500;
+       proj.event_damage = func_null;
+       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, p, TRUE);
+}
+
+float spider_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monster_melee(self.enemy, autocvar_g_monster_spider_attack_stand_damage, 0.3, DEATH_MONSTER_SPIDER, TRUE);
+                       monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2);
+                       self.attack_finished_single = time + autocvar_g_monster_spider_attack_stand_delay;
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(self.enemy.frozen)
+                               return FALSE;
+                       
+                       monsters_setframe(spider_anim_attack2);
+                       self.attack_finished_single = time + autocvar_g_monster_spider_attack_leap_delay;
+                       monster_makevectors(self.enemy);
+                       spider_shootweb(self.spider_type);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spider_die()
+{
+       Monster_CheckDropCvars ("spider");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(spider_anim_attack);
+       self.angles += '180 0 0';
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void spider_spawn() 
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_spider_health;
+       
+       self.classname                  = "monster_spider";
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.monster_attackfunc = spider_attack;
+       self.think                              = spider_think;
+       
+       monsters_setframe(spider_anim_idle);
+       
+       monster_setupsounds("spider");
+       
+       if not(self.spider_type)
+               self.spider_type = autocvar_g_monster_spider_attack_type;
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_spider() 
+{
+       if not(autocvar_g_monster_spider) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_spider;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not (monster_initialize(
+                        "Spider", MONSTER_SPIDER,
+                        SPIDER_MIN, SPIDER_MAX,
+                        FALSE,
+                        spider_die, spider_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/stingray.qc b/qcsrc/server/monsters/monster/stingray.qc
new file mode 100644 (file)
index 0000000..61dccfb
--- /dev/null
@@ -0,0 +1,95 @@
+const vector STINGRAY_MIN = '-20 -20 -31';
+const vector STINGRAY_MAX = '20 20 20';
+
+string STINGRAY_MODEL = "models/monsters/fish.mdl";
+
+#ifdef SVQC
+float autocvar_g_monster_stingray;
+float autocvar_g_monster_stingray_health;
+float autocvar_g_monster_stingray_damage;
+float autocvar_g_monster_stingray_speed_walk;
+float autocvar_g_monster_stingray_speed_run;
+
+const float stingray_anim_attack = 0;
+const float stingray_anim_death  = 1;
+const float stingray_anim_swim   = 2;
+const float stingray_anim_pain   = 3;
+
+void stingray_think()
+{
+       self.think = stingray_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_stingray_speed_run, autocvar_g_monster_stingray_speed_walk, 10, stingray_anim_swim, stingray_anim_swim, stingray_anim_swim);
+}
+
+float stingray_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(stingray_anim_attack);
+                       self.attack_finished_single = time + 0.5;
+                       monster_melee(self.enemy, autocvar_g_monster_stingray_damage, 0.1, DEATH_MONSTER_STINGRAY, FALSE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+       }
+       
+       return FALSE;
+}
+
+void stingray_die()
+{
+       Monster_CheckDropCvars ("stingray");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe(stingray_anim_death);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void stingray_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_stingray_health;
+
+       self.damageforcescale   = 0.5;
+       self.classname                  = "monster_stingray";
+       self.monster_attackfunc = stingray_attack;
+       self.flags                         |= FL_SWIM;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = stingray_think;
+       
+       monster_setupsounds("stingray");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_stingray()
+{
+       if not(autocvar_g_monster_stingray) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_stingray;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       self.scale = 1.3;
+       
+       if not (monster_initialize(
+                        "Stingray", MONSTER_STINGRAY,
+                        STINGRAY_MIN, STINGRAY_MAX,
+                        TRUE,
+                        stingray_die, stingray_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/wyvern.qc b/qcsrc/server/monsters/monster/wyvern.qc
new file mode 100644 (file)
index 0000000..e9eda6d
--- /dev/null
@@ -0,0 +1,154 @@
+const vector WYVERN_MIN = '-20 -20 -58';
+const vector WYVERN_MAX = '20 20 20';
+
+string WYVERN_MODEL = "models/monsters/wizard.mdl";
+
+#ifdef SVQC
+float autocvar_g_monster_wyvern;
+float autocvar_g_monster_wyvern_health;
+float autocvar_g_monster_wyvern_speed_walk;
+float autocvar_g_monster_wyvern_speed_run;
+float autocvar_g_monster_wyvern_fireball_damage;
+float autocvar_g_monster_wyvern_fireball_force;
+float autocvar_g_monster_wyvern_fireball_radius;
+float autocvar_g_monster_wyvern_fireball_edgedamage;
+float autocvar_g_monster_wyvern_fireball_damagetime;
+float autocvar_g_monster_wyvern_fireball_speed;
+
+const float wyvern_anim_hover  = 0;
+const float wyvern_anim_fly    = 1;
+const float wyvern_anim_magic  = 2;
+const float wyvern_anim_pain   = 3;
+const float wyvern_anim_death  = 4;
+
+void wyvern_think()
+{
+       self.think = wyvern_think;
+       self.nextthink = time + self.ticrate;
+       
+       monster_move(autocvar_g_monster_wyvern_speed_run, autocvar_g_monster_wyvern_speed_walk, 300, wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
+}
+
+void wyvern_fireball_explode()
+{
+       entity e;
+       if(self)
+       {
+               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage(self, self.realowner, autocvar_g_monster_wyvern_fireball_damage, autocvar_g_monster_wyvern_fireball_edgedamage, autocvar_g_monster_wyvern_fireball_force, world, autocvar_g_monster_wyvern_fireball_radius, self.projectiledeathtype, world);
+               
+               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_wyvern_fireball_radius)
+                       Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_wyvern_fireball_damagetime, self.projectiledeathtype);
+               
+               remove(self);
+       }
+}
+
+void wyvern_fireball_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       wyvern_fireball_explode();
+}
+
+void wyvern_fireball()
+{
+       entity missile = spawn();
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       
+       monster_makevectors(self.enemy);
+
+       missile.owner = missile.realowner = self;
+       missile.solid = SOLID_TRIGGER;
+       missile.movetype = MOVETYPE_FLYMISSILE;
+       missile.projectiledeathtype = DEATH_MONSTER_WYVERN;
+       setsize(missile, '-6 -6 -6', '6 6 6');          
+       setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
+       missile.flags = FL_PROJECTILE;
+       missile.velocity = dir * autocvar_g_monster_wyvern_fireball_speed;
+       missile.avelocity = '300 300 300';
+       missile.nextthink = time + 5;
+       missile.think = wyvern_fireball_explode;
+       missile.enemy = self.enemy;
+       missile.touch = wyvern_fireball_touch;
+       CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
+}
+
+float wyvern_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               case MONSTER_ATTACK_RANGED:
+               {
+                       self.attack_finished_single = time + 1.2;
+                       
+                       wyvern_fireball();
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void wyvern_die()
+{
+       Monster_CheckDropCvars ("wyvern");
+       
+       self.think                      = monster_dead_think;
+       self.nextthink          = time + self.ticrate;
+       self.ltime                      = time + 5;
+       self.velocity_x         = -200 + 400 * random();
+       self.velocity_y         = -200 + 400 * random();
+       self.velocity_z         = 100 + 100 * random();
+       
+       monsters_setframe(wyvern_anim_death);
+       
+       monster_hook_death(); // for post-death mods
+}
+
+void wyvern_spawn()
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_wyvern_health;
+       
+       self.classname                  = "monster_wyvern";
+       self.monster_attackfunc = wyvern_attack;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.movetype                   = MOVETYPE_FLY;
+       self.flags                         |= FL_FLY;
+       self.think                              = wyvern_think;
+       
+       monster_setupsounds("wyvern");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_wyvern()
+{
+       if not(autocvar_g_monster_wyvern) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_wyvern;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       self.scale = 1.3;
+       
+       if not (monster_initialize(
+                        "Wyvern", MONSTER_WYVERN,
+                        WYVERN_MIN, WYVERN_MAX,
+                        TRUE,
+                        wyvern_die, wyvern_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
+
+#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/zombie.qc b/qcsrc/server/monsters/monster/zombie.qc
new file mode 100644 (file)
index 0000000..aa96920
--- /dev/null
@@ -0,0 +1,165 @@
+const vector ZOMBIE_MIN = '-18 -18 -25';
+const vector ZOMBIE_MAX = '18 18 47';
+
+string ZOMBIE_MODEL = "models/monsters/zombie.dpm";
+
+#ifdef SVQC
+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;
+
+const float zombie_anim_attackleap                     = 0;
+const float zombie_anim_attackrun1                     = 1;
+const float zombie_anim_attackrun2                     = 2;
+const float zombie_anim_attackrun3                     = 3;
+const float zombie_anim_attackstanding1                = 4;
+const float zombie_anim_attackstanding2                = 5;
+const float zombie_anim_attackstanding3                = 6;
+const float zombie_anim_blockend                       = 7;
+const float zombie_anim_blockstart                     = 8;
+const float zombie_anim_deathback1                     = 9;
+const float zombie_anim_deathback2                     = 10;
+const float zombie_anim_deathback3                     = 11;
+const float zombie_anim_deathfront1                    = 12;
+const float zombie_anim_deathfront2                    = 13;
+const float zombie_anim_deathfront3                    = 14;
+const float zombie_anim_deathleft1                     = 15;
+const float zombie_anim_deathleft2                     = 16;
+const float zombie_anim_deathright1                    = 17;
+const float zombie_anim_deathright2                    = 18;
+const float zombie_anim_idle                           = 19;
+const float zombie_anim_painback1                      = 20;
+const float zombie_anim_painback2                      = 21;
+const float zombie_anim_painfront1                     = 22;
+const float zombie_anim_painfront2                     = 23;
+const float zombie_anim_runbackwards           = 24;
+const float zombie_anim_runbackwardsleft       = 25;
+const float zombie_anim_runbackwardsright      = 26;
+const float zombie_anim_runforward                     = 27;
+const float zombie_anim_runforwardleft         = 28;
+const float zombie_anim_runforwardright                = 29;
+const float zombie_anim_spawn                          = 30;
+
+void zombie_think()
+{
+       self.think = zombie_think;
+       self.nextthink = time + self.ticrate;
+
+       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_attack_leap_touch()
+{
+       if (self.health <= 0)
+               return;
+               
+       vector angles_face;
+
+       if(other.takedamage)
+       {
+               angles_face = vectoangles(self.moveto - self.origin);
+               angles_face = normalize(angles_face) * autocvar_g_monster_zombie_attack_leap_force;
+               Damage(other, self, self, autocvar_g_monster_zombie_attack_leap_damage * monster_skill, DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
+               self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+       }
+
+       if (trace_dphitcontents)
+               self.touch = MonsterTouch;
+}
+
+float zombie_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float rand = random(), chosen_anim;
+               
+                       if(rand < 0.33)
+                               chosen_anim = zombie_anim_attackstanding1;
+                       else if(rand < 0.66)
+                               chosen_anim = zombie_anim_attackstanding2;
+                       else
+                               chosen_anim = zombie_anim_attackstanding3;
+                               
+                       monsters_setframe(chosen_anim);
+
+                       self.attack_finished_single = time + autocvar_g_monster_zombie_attack_stand_delay;
+                       
+                       monster_melee(self.enemy, autocvar_g_monster_zombie_attack_stand_damage, 0.3, DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_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_die()
+{
+       Monster_CheckDropCvars ("zombie");
+       
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+       self.ltime = time + 5;
+       monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
+               
+       monster_hook_death(); // for post-death mods
+}
+
+void zombie_spawn() 
+{
+       if not(self.health)
+               self.health = autocvar_g_monster_zombie_health;
+       
+       self.classname                  = "monster_zombie";
+       self.spawn_time                 = time + 2.1;
+       self.nextthink                  = time + random() * 0.5 + 0.1;
+       self.think                              = zombie_think;
+       self.monster_attackfunc = zombie_attack;
+       self.spawnshieldtime    = self.spawn_time;
+       self.respawntime                = 0.1;
+       self.spawnflags            |= MONSTER_RESPAWN_DEATHPOINT; // always enabled for zombie
+       
+       monsters_setframe(zombie_anim_spawn);
+       
+       monster_setupsounds("zombie");
+       
+       monster_hook_spawn(); // for post-spawn mods
+}
+
+void spawnfunc_monster_zombie() 
+{
+       if not(autocvar_g_monster_zombie) { remove(self); return; }
+       
+       self.monster_spawnfunc = spawnfunc_monster_zombie;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not (monster_initialize(
+                        "Zombie", MONSTER_ZOMBIE,
+                        ZOMBIE_MIN, ZOMBIE_MAX,
+                        FALSE,
+                        zombie_die, zombie_spawn))
+       {
+               remove(self);
+               return;
+       }
+}
+
+#endif //SVQC
diff --git a/qcsrc/server/monsters/monsters.qh b/qcsrc/server/monsters/monsters.qh
new file mode 100644 (file)
index 0000000..53b5a13
--- /dev/null
@@ -0,0 +1,20 @@
+// Lib
+#ifdef SVQC
+#include "lib/defs.qh"
+#include "lib/monsters.qc"
+#include "lib/spawn.qc"
+#endif
+
+// Monsters
+#include "monster/brute.qc"
+#include "monster/animus.qc"
+#include "monster/shambler.qc"
+#include "monster/bruiser.qc"
+#include "monster/wyvern.qc"
+#include "monster/cerberus.qc"
+#include "monster/slime.qc"
+#include "monster/knight.qc"
+#include "monster/stingray.qc"
+#include "monster/mage.qc"
+#include "monster/zombie.qc"
+#include "monster/spider.qc"
index 9b9f7fde01abe5dc22dcd51310665c265aa15e6d..b8f46b534dd5c09c0da8331f4ff9325e62b8c279 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 * self.gravity;
+}
+
+void movelib_jump_simple(float power){
+    self.velocity_z=power;
+    self.movelib_lastupdate = time;
+}
+
 /*
 .float mass;
 .float side_friction;
index ee0da7e0cd9eb1ae4d67217bed3841521b53a95b..4f33594d9a723c903ff2e41da0e1f1e9bb3ef4a8 100644 (file)
@@ -123,6 +123,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
 
@@ -147,6 +157,39 @@ 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:
+               .void() monster_loot;
+               entity other;
+       
+MUTATOR_HOOKABLE(MonsterMove);
+       // called when a monster moves
+       // returning TRUE makes the monster stop
+       // 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.
index 1de05661bed63d57a275f61c8d0709d2fb8c7232..3ff0fc96c5609ee0541390c7fdc8fa4c7ca83a9b 100644 (file)
@@ -451,14 +451,19 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
 void ctf_Handle_Return(entity flag, entity player)
 {
        // messages and sounds
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
+       if(IS_PLAYER(player))
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
+               
        Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
        sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
        ctf_EventLog("return", flag.team, player);
 
        // scoring
-       PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
-       PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+       if(IS_PLAYER(player))
+       {
+               PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
+               PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+       }
 
        TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
        
@@ -809,6 +814,8 @@ void ctf_FlagTouch()
        
        entity toucher = other;
        
+       if(toucher.frozen) { return; }
+       
        // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
        if(ITEM_TOUCH_NEEDKILL())
        {
@@ -825,6 +832,11 @@ void ctf_FlagTouch()
                else
                        return; // do nothing
        }
+       else if(toucher.flags & FL_MONSTER)
+       {
+               if not(autocvar_g_ctf_allow_monster_touch)
+                       return; // do nothing
+       }
        else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
        {
                if(time > self.wait) // if we haven't in a while, play a sound/effect
@@ -841,9 +853,9 @@ void ctf_FlagTouch()
        {       
                case FLAG_BASE:
                {
-                       if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
+                       if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self) && !(toucher.flags & FL_MONSTER))
                                ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
+                       else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && !(toucher.flags & FL_MONSTER))
                                ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
                        break;
                }
@@ -852,7 +864,7 @@ void ctf_FlagTouch()
                {
                        if(!IsDifferentTeam(toucher, self))
                                ctf_Handle_Return(self, toucher); // toucher just returned his own flag
-                       else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+                       else if(!(toucher.flags & FL_MONSTER) && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
                                ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
                        break;
                }
index 82147826ee56345cad3162601515a20c05e631db..325b6c29c891633af5dab355785051e96230853f 100644 (file)
@@ -1,7 +1,6 @@
 .float freezetag_frozen_time;
 .float freezetag_frozen_timeout;
 .float freezetag_revive_progress;
-.entity freezetag_ice;
 #define ICE_MAX_ALPHA 1
 #define ICE_MIN_ALPHA 0.1
 float freezetag_teams;
@@ -14,22 +13,22 @@ void freezetag_count_alive_players()
                if(e.team == NUM_TEAM_1 && e.health >= 1)
                {
                        ++total_players;
-                       if (!e.freezetag_frozen) ++redalive;
+                       if (e.frozen != 1) ++redalive;
                }
                else if(e.team == NUM_TEAM_2 && e.health >= 1)
                {
                        ++total_players;
-                       if (!e.freezetag_frozen) ++bluealive;
+                       if (e.frozen != 1) ++bluealive;
                }
                else if(e.team == NUM_TEAM_3 && e.health >= 1)
                {
                        ++total_players;
-                       if (!e.freezetag_frozen) ++yellowalive;
+                       if (e.frozen != 1) ++yellowalive;
                }
                else if(e.team == NUM_TEAM_4 && e.health >= 1)
                {
                        ++total_players;
-                       if (!e.freezetag_frozen) ++pinkalive;
+                       if (e.frozen != 1) ++pinkalive;
                }
        }
        FOR_EACH_REALCLIENT(e) {
@@ -128,15 +127,6 @@ float freezetag_CheckWinner()
        return 1;
 }
 
-// this is needed to allow the player to turn his view around (fixangle can't
-// be used to freeze his view, as that also changes the angles), while not
-// turning that ice object with the player
-void freezetag_Ice_Think()
-{
-       setorigin(self, self.owner.origin - '0 0 16');
-       self.nextthink = time;
-}
-
 void freezetag_Add_Score(entity attacker)
 {
        if(attacker == self)
@@ -157,54 +147,24 @@ void freezetag_Add_Score(entity attacker)
 
 void freezetag_Freeze(entity attacker)
 {
-       if(self.freezetag_frozen)
+       if(self.frozen)
                return;
-       self.freezetag_frozen = 1;
-       self.freezetag_frozen_time = time;
-       self.freezetag_revive_progress = 0;
-       self.health = 1;
-       if(autocvar_g_freezetag_frozen_maxtime > 0)
-               self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
+       
+       Freeze(self, 0, 1, TRUE);
+       
        freezetag_count_alive_players();
 
-       entity ice;
-       ice = spawn();
-       ice.owner = self;
-       ice.classname = "freezetag_ice";
-       ice.think = freezetag_Ice_Think;
-       ice.nextthink = time;
-       ice.frame = floor(random() * 21); // ice model has 20 different looking frames
-       ice.alpha = ICE_MAX_ALPHA;
-       ice.colormod = Team_ColorRGB(self.team);
-       ice.glowmod = ice.colormod;
-       setmodel(ice, "models/ice/ice.md3");
-
-       self.freezetag_ice = ice;
-
-       RemoveGrapplingHook(self);
-
-       // add waypoint
-       WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
-
        freezetag_Add_Score(attacker);
 }
 
 void freezetag_Unfreeze(entity attacker)
 {
-       self.freezetag_frozen = 0;
        self.freezetag_frozen_time = 0;
        self.freezetag_frozen_timeout = 0;
-       self.freezetag_revive_progress = 0;
-
-       remove(self.freezetag_ice);
-       self.freezetag_ice = world;
-
-       if(self.waypointsprite_attached)
-               WaypointSprite_Kill(self.waypointsprite_attached);
+       
+       Unfreeze(self);
 }
 
-
 // ================
 // Bot player logic
 // ================
@@ -221,7 +181,7 @@ void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradiu
        {
                if ((head != self) && (head.team == self.team))
                {
-                       if (head.freezetag_frozen)
+                       if (head.frozen == 1)
                        {
                                distance = vlen(head.origin - org);
                                if (distance > sradius)
@@ -253,12 +213,12 @@ void havocbot_role_ft_offense()
        unfrozen = 0;
        FOR_EACH_PLAYER(head)
        {
-               if ((head.team == self.team) && (!head.freezetag_frozen))
+               if ((head.team == self.team) && (head.frozen != 1))
                        unfrozen++;
        }
 
        // If only one left on team or if role has timed out then start trying to free players.
-       if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
+       if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout))
        {
                dprint("changing role to freeing\n");
                self.havocbot_role = havocbot_role_ft_freeing;
@@ -326,7 +286,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
        if(round_handler_IsActive())
        if(round_handler_CountdownRunning())
        {
-               if(self.freezetag_frozen)
+               if(self.frozen)
                        freezetag_Unfreeze(world);
                freezetag_count_alive_players();
                return 1; // let the player die so that he can respawn whenever he wants
@@ -338,7 +298,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
                || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
        {
                // let the player die, he will be automatically frozen when he respawns
-               if(!self.freezetag_frozen)
+               if(self.frozen != 1)
                {
                        freezetag_Add_Score(frag_attacker);
                        freezetag_count_alive_players();
@@ -349,7 +309,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
                return 1;
        }
 
-       if(self.freezetag_frozen)
+       if(self.frozen)
                return 1;
 
        freezetag_Freeze(frag_attacker);
@@ -369,8 +329,6 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
                Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
        }
 
-       frag_target.health = 1; // "respawn" the player :P
-
        return 1;
 }
 
@@ -401,7 +359,7 @@ MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
 {
        FOR_EACH_PLAYER(self)
        {
-               if (self.freezetag_frozen)
+               if (self.frozen)
                        freezetag_Unfreeze(world);
                self.freezetag_frozen_timeout = -1;
                PutClientInServer();
@@ -425,7 +383,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
        if(gameover)
                return 1;
 
-       if(self.freezetag_frozen)
+       if(self.frozen == 1)
        {
                // keep health = 1
                self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
@@ -438,7 +396,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
        entity o;
        o = world;
        if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
-               self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
+               self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
 
        if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
                n = -1;
@@ -448,7 +406,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
                n = 0;
                FOR_EACH_PLAYER(other) if(self != other)
                {
-                       if(other.freezetag_frozen == 0)
+                       if(!other.frozen)
                        {
                                if(other.team == self.team)
                                {
@@ -456,7 +414,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
                                        {
                                                if(!o)
                                                        o = other;
-                                               if(self.freezetag_frozen)
+                                               if(self.frozen == 1)
                                                        other.reviving = TRUE;
                                                ++n;
                                        }
@@ -465,12 +423,12 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
                }
        }
 
-       if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us
+       if(n && self.frozen == 1) // OK, there is at least one teammate reviving us
        {
-               self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
-               self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
+               self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
+               self.health = max(1, self.revive_progress * autocvar_g_balance_health_start);
 
-               if(self.freezetag_revive_progress >= 1)
+               if(self.revive_progress >= 1)
                {
                        freezetag_Unfreeze(self);
                        freezetag_count_alive_players();
@@ -501,37 +459,27 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
                {
                        if(other.reviving)
                        {
-                               other.freezetag_revive_progress = self.freezetag_revive_progress;
+                               other.revive_progress = self.revive_progress;
                                other.reviving = FALSE;
                        }
                }
        }
-       else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset
+       else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
        {
-               self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
-               self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
+               self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
+               self.health = max(1, self.revive_progress * autocvar_g_balance_health_start);
        }
-       else if(!n)
+       else if(!n && !self.frozen)
        {
-               self.freezetag_revive_progress = 0; // thawing nobody
+               self.revive_progress = 0; // thawing nobody
        }
 
        return 1;
 }
 
-MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
-{
-       if(self.freezetag_frozen)
-       {
-               self.movement = '0 0 0';
-               self.disableclientprediction = 1;
-       }
-       return 1;
-}
-
 MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
 {
-       if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER)
+       if(frag_target.frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER)
        {
                frag_damage = 0;
                frag_force = frag_force * autocvar_g_freezetag_frozen_force;
@@ -539,20 +487,6 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
        return 1;
 }
 
-MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
-{
-       if (self.freezetag_frozen)
-               return 1;
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(freezetag_ItemTouch)
-{
-       if (other.freezetag_frozen)
-               return MUT_ITEMTOUCH_RETURN;
-       return MUT_ITEMTOUCH_CONTINUE;
-}
-
 MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
 {
        if not(self.deadflag)
@@ -566,13 +500,6 @@ MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
        return TRUE;
 }
 
-MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy)
-{
-       self.freezetag_frozen = other.freezetag_frozen;
-       self.freezetag_revive_progress = other.freezetag_revive_progress;
-       return 0;
-}
-
 MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
 {
        freezetag_teams = autocvar_g_freezetag_teams_override;
@@ -583,14 +510,6 @@ MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
        return 0;
 }
 
-MUTATOR_HOOKFUNCTION(freezetag_VehicleTouch)
-{
-       if(other.freezetag_frozen)
-               return TRUE;
-               
-       return FALSE;
-}
-
 void freezetag_Initialize()
 {
        precache_model("models/ice/ice.md3");
@@ -603,9 +522,6 @@ void freezetag_Initialize()
        addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
        addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
        addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
-
-       addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
-       addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
 }
 
 MUTATOR_DEFINITION(gamemode_freezetag)
@@ -617,14 +533,9 @@ MUTATOR_DEFINITION(gamemode_freezetag)
        MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
        MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
        MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
-       MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
        MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
-       MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY);
-       MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY);
        MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY);
-       MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY);
        MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
-       MUTATOR_HOOK(VehicleTouch, freezetag_VehicleTouch, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
index e4be2d7ab4ce2ea2b9422456b2affc7fbec3f2fd..c31d53efca3f4f98c9b73d637f4189023b548d46 100644 (file)
@@ -639,28 +639,6 @@ void onslaught_generator_damage(entity inflictor, entity attacker, float damage,
                onslaught_updatelinks();
        }
 
-       if(self.health <= 0)
-               setmodel(self, "models/onslaught/generator_dead.md3");
-       else if(self.health < self.max_health * 0.10)
-               setmodel(self, "models/onslaught/generator_dmg9.md3");
-       else if(self.health < self.max_health * 0.20)
-               setmodel(self, "models/onslaught/generator_dmg8.md3");
-       else if(self.health < self.max_health * 0.30)
-               setmodel(self, "models/onslaught/generator_dmg7.md3");
-       else if(self.health < self.max_health * 0.40)
-               setmodel(self, "models/onslaught/generator_dmg6.md3");
-       else if(self.health < self.max_health * 0.50)
-               setmodel(self, "models/onslaught/generator_dmg5.md3");
-       else if(self.health < self.max_health * 0.60)
-               setmodel(self, "models/onslaught/generator_dmg4.md3");
-       else if(self.health < self.max_health * 0.70)
-               setmodel(self, "models/onslaught/generator_dmg3.md3");
-       else if(self.health < self.max_health * 0.80)
-               setmodel(self, "models/onslaught/generator_dmg2.md3");
-       else if(self.health < self.max_health * 0.90)
-               setmodel(self, "models/onslaught/generator_dmg1.md3");
-       setsize(self, '-52 -52 -14', '52 52 75');
-
        // Throw some flaming gibs on damage, more damage = more chance for gib
        if(random() < damage/220)
        {
@@ -689,6 +667,8 @@ void onslaught_generator_damage(entity inflictor, entity attacker, float damage,
        if(random() < damage/200+0.2)
                if(random() < 0.5)
                        ons_throwgib(hitloc + '0 0 20', randomvec()*360, "models/onslaught/gen_gib1.md3", 5, FALSE);
+                       
+       self.SendFlags |= GSF_STATUS;
 }
 
 // update links after a delay
@@ -696,8 +676,9 @@ void onslaught_generator_delayed()
 {
        onslaught_updatelinks();
        // now begin normal thinking
-       self.think = onslaught_generator_think;
-       self.nextthink = time;
+       generator_link(onslaught_generator_think);
+       
+       self.SendFlags = GSF_SETUP;
 }
 
 string onslaught_generator_waypointsprite_for_team(entity e, float t)
@@ -852,7 +833,9 @@ void onslaught_generator_reset()
        self.think = onslaught_generator_delayed;
        self.nextthink = time + 0.2;
        setmodel(self, "models/onslaught/generator.md3");
-       setsize(self, '-52 -52 -14', '52 52 75');
+       setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+       
+       self.SendFlags |= GSF_STATUS;
 
        if(!self.noalign)
        {
@@ -884,16 +867,6 @@ void spawnfunc_onslaught_generator()
        //entity e;
        precache_model("models/onslaught/generator.md3");
        precache_model("models/onslaught/generator_shield.md3");
-       precache_model("models/onslaught/generator_dmg1.md3");
-       precache_model("models/onslaught/generator_dmg2.md3");
-       precache_model("models/onslaught/generator_dmg3.md3");
-       precache_model("models/onslaught/generator_dmg4.md3");
-       precache_model("models/onslaught/generator_dmg5.md3");
-       precache_model("models/onslaught/generator_dmg6.md3");
-       precache_model("models/onslaught/generator_dmg7.md3");
-       precache_model("models/onslaught/generator_dmg8.md3");
-       precache_model("models/onslaught/generator_dmg9.md3");
-       precache_model("models/onslaught/generator_dead.md3");
        precache_model("models/onslaught/shockwave.md3");
        precache_model("models/onslaught/shockwavetransring.md3");
        precache_model("models/onslaught/gen_gib1.md3");
@@ -908,6 +881,7 @@ void spawnfunc_onslaught_generator()
        precache_sound("onslaught/ons_hit1.wav");
        precache_sound("onslaught/ons_hit2.wav");
        precache_sound("onslaught/electricity_explode.wav");
+       precache_sound("onslaught/generator_underattack.wav");
        if (!self.team)
                objerror("team must be set");
        
@@ -923,7 +897,7 @@ void spawnfunc_onslaught_generator()
        self.movetype = MOVETYPE_NONE;
        self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
        setmodel(self, "models/onslaught/generator.md3");
-       setsize(self, '-52 -52 -14', '52 52 75');
+       setsize(self, GENERATOR_MIN, GENERATOR_MAX);
        setorigin(self, self.origin);
        self.takedamage = DAMAGE_AIM;
        self.bot_attack = TRUE;
@@ -1644,11 +1618,48 @@ MUTATOR_HOOKFUNCTION(ons_PlayerSpawn)
     return 0;
 }
 
+MUTATOR_HOOKFUNCTION(ons_MonsterThink)
+{
+       entity e = find(world, targetname, self.target);
+       if (e != world)
+               self.team = e.team;
+               
+       self.SendFlags |= MSF_STATUS; // update team
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ons_MonsterSpawn)
+{
+       entity e, ee = world;
+       
+       if(self.targetname)
+       {
+               e = find(world,target,self.targetname);
+               if(e != world)
+               {
+                       self.team = e.team;
+                       self.SendFlags |= MSF_STATUS;
+                       ee = e;
+               }
+       }
+       
+       if(ee)
+       {
+        activator = ee;
+        self.use();
+    }
+
+       return FALSE;
+}
+
 MUTATOR_DEFINITION(gamemode_onslaught)
 {
        MUTATOR_HOOK(BuildMutatorsPrettyString, ons_BuildMutatorsPrettyString, CBC_ORDER_ANY);
        MUTATOR_HOOK(BuildMutatorsString, ons_BuildMutatorsString, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerSpawn, ons_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterMove, ons_MonsterThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterSpawn, ons_MonsterSpawn, CBC_ORDER_ANY);
        //MUTATOR_HOOK(Spawn_Score, ons_Spawn_Score, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qc b/qcsrc/server/mutators/gamemode_towerdefense.qc
new file mode 100644 (file)
index 0000000..b3a4cfe
--- /dev/null
@@ -0,0 +1,931 @@
+void td_debug(string input)
+{
+       switch(autocvar_g_td_debug)
+       {
+               case 1: dprint(input); break;
+               case 2: print(input); break;
+       }
+}
+
+void td_waypoint_link(float tm, vector from, vector to)
+{
+       switch(tm)
+       {
+               case NUM_TEAM_1:
+                       WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_red"), from, to);
+                       break;
+               case NUM_TEAM_2:
+                       WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_blue"), from, to);
+                       break;
+               case NUM_TEAM_3:
+                       WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_yellow"), from, to);
+                       break;
+               case NUM_TEAM_4:
+                       WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_pink"), from, to);
+                       break;
+       }
+}
+
+void td_waypoint_think()
+{
+       entity e = world;
+       if(gameover)
+       {
+               remove(self);
+               return;
+       }
+       
+       if not(self.team)
+       {
+               e = find(world, target, self.targetname);
+               if(e)
+                       self.team = e.team;
+       }
+       if not(self.team)
+       {
+               e = find(world, target2, self.targetname);
+               if(e)
+                       self.team = e.team;
+       }
+       if not(self.team)
+       {
+               e = find(world, target3, self.targetname);
+               if(e)
+                       self.team = e.team;
+       }
+       if not(self.team)
+       {
+               e = find(world, target4, self.targetname);
+               if(e)
+                       self.team = e.team;
+       }
+       
+       if not(self.team)
+       {
+               td_debug("Tower Defense waypoint without a team, removing it.\n");
+               remove(self);
+               return;
+       }
+       
+       if(time >= self.last_trace)
+       {
+               entity e;
+               
+               e = find(world, targetname, self.target);
+               if(e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               e = find(world, targetname, self.target2);
+               if(e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               e = find(world, targetname, self.target3);
+               if(e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               e = find(world, targetname, self.target4);
+               if(e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+                       
+               e = find(world, target, self.targetname);
+               if(e.classname == "td_spawnpoint" || e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               e = find(world, target2, self.targetname);
+               if(e.classname == "td_spawnpoint" || e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               e = find(world, target3, self.targetname);
+               if(e.classname == "td_spawnpoint" || e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               e = find(world, target4, self.targetname);
+               if(e.classname == "td_spawnpoint" || e.classname == "td_waypoint" || e.flags & FL_GENERATOR)
+                       td_waypoint_link(self.team, self.origin, e.origin);
+               
+               self.last_trace = time + 0.5;
+       }
+       
+       self.nextthink = time + 0.1;
+}
+
+void td_generator_die() 
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(":gendestroyed");
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
+       
+       self.solid                      = SOLID_NOT;
+       self.takedamage         = DAMAGE_NO;
+       self.event_damage   = func_null;
+       self.enemy                      = world;
+       self.reset                      = func_null; // don't reset this generator
+       
+       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 || self.takedamage == DAMAGE_NO)
+               return;
+               
+       entity head;
+               
+       if (time > self.pain_finished)
+       {
+               self.pain_finished = time + 10;
+               play2team(self.team, "onslaught/generator_underattack.wav");
+       }
+       
+       if (random() < 0.5)
+               spamsound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTN_NORM);
+       else
+               spamsound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM);
+       
+       
+       FOR_EACH_REALPLAYER(head)
+       if(!IsDifferentTeam(head, self))
+               Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_TD_GENDAMAGED);
+       
+       self.health -= damage;
+       
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+               
+       if(self.health <= 0)
+       {
+               FOR_EACH_PLAYER(head)
+               if(!IsDifferentTeam(head, attacker))
+                       PlayerScore_Add(head, SP_TD_DESTROYS, 1);
+               
+               TeamScore_AddToTeam(attacker.team, ST_TD_DESTROYS, 1);
+               td_generator_die();
+       }
+               
+       self.SendFlags |= GSF_STATUS;
+}
+
+void td_generator_setup()
+{
+       self.think                      = func_null;
+       self.nextthink          = -1;
+       self.solid                      = SOLID_BBOX;
+       self.takedamage         = DAMAGE_AIM;
+       self.event_damage       = td_generator_damage;
+       self.movetype           = MOVETYPE_NONE;
+       self.monster_attack     = TRUE;
+       self.netname            = "Generator";
+       self.reset                      = func_null;
+       
+       WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 90', self, sprite, RADARICON_OBJECTIVE, Team_ColorRGB(self.team));   
+       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+entity PickSpawn (float tm)
+{
+       entity e;
+       RandomSelection_Init();
+       for(e = world;(e = find(e, classname, "td_spawnpoint")); )
+       if(e.team == tm)
+               RandomSelection_Add(e, 0, string_null, 1, 1);
+
+       return RandomSelection_chosen_ent;
+}
+
+void TD_SpawnMonster(float tm, float monster)
+{
+       entity e, mon;
+       
+       e = PickSpawn(tm);
+               
+       if(e == world)
+       {
+               td_debug("Warning: couldn't find any td_spawnpoint spawnpoints, no monsters will spawn!\n");
+               return;
+       }
+  
+       mon = spawnmonster("", monster, e, e, e.origin, FALSE, 2);
+       if(e.target2)
+       {
+               if(random() <= 0.5 && e.target)
+                       mon.target2 = e.target;
+               else
+                       mon.target2 = e.target2;
+       }
+       else
+               mon.target2 = e.target;
+}
+
+float RandomMonster()
+{
+       RandomSelection_Init();
+       
+       float i;
+       
+       for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
+       {
+               if(i == MONSTER_STINGRAY || i == MONSTER_WYVERN)
+                       continue; // flying/swimming monsters not yet supported
+               
+               RandomSelection_Add(world, i, "", 1, 1);
+       }
+       
+       return RandomSelection_chosen_float;
+}
+
+void SpawnMonsters(float tm)
+{
+       float whichmon;
+       
+       whichmon = RandomMonster();
+       
+       TD_SpawnMonster(tm, whichmon);
+}
+
+entity PickGenerator(float tm)
+{
+       entity head;
+       
+       RandomSelection_Init();
+       for(head = world;(head = findflags(head, flags, FL_GENERATOR)); )
+       if(head.team != tm)
+               RandomSelection_Add(head, 0, string_null, 1, 1);
+       
+       return RandomSelection_chosen_ent;
+}
+
+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_MULTI, MULTI_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)) { td_debug("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.team = own.team;
+       
+       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;
+               default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return;
+       }
+       
+       Send_Notification(NOTIF_ONE, spawnedby, MSG_MULTI, MULTI_TD_SPAWN);
+               
+       self = oldself;
+}
+
+void buffturret(entity tur, float buff)
+{
+       float refbuff = bound(0.01, buff * 0.05, 0.1);
+       
+       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_radius       *= buff;
+    tur.shot_speed        *= buff;
+    tur.shot_spread       *= buff;
+    tur.shot_force        *= buff;
+       
+       if(buff < 1)
+               tur.shot_refire += refbuff;
+       else
+               tur.shot_refire -= refbuff;
+}
+
+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 td_generator_delayed()
+{
+       generator_link(td_generator_setup);
+       
+       self.SendFlags = GSF_SETUP;
+}
+
+// round handling
+#define TD_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0))
+#define TD_ALIVE_TEAMS_OK() (TD_ALIVE_TEAMS() == 2)
+void TD_RoundStart()
+{
+       entity head;
+       
+       allowed_to_spawn = TRUE;
+       
+       ignore_turrets = TRUE;
+       
+       FOR_EACH_PLAYER(head)
+               head.ready = FALSE;
+       
+       total_killed = 0;
+}
+
+void TD_count_alive_monsters()
+{
+       entity head;
+       
+       total_alive = 0;
+       redalive = 0;
+       bluealive = 0;
+       
+       FOR_EACH_MONSTER(head)
+       {
+               if(head.health <= 0) continue;
+               
+               ++total_alive;
+               
+               switch(head.team)
+               {
+                       case NUM_TEAM_1: ++redalive; break;
+                       case NUM_TEAM_2: ++bluealive; break;
+               }
+       }
+}
+
+float TD_GetWinnerTeam()
+{
+       float winner_team = 0;
+       if(redalive >= 1)
+               winner_team = NUM_TEAM_1;
+       if(bluealive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_2;
+       }
+       if(winner_team)
+               return winner_team;
+       return -1; // no monster left
+}
+
+float TD_CheckWinner()
+{
+       entity head = world;
+       
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+               round_handler_Init(5, 10, 180);
+               FOR_EACH_MONSTER(head) if(head.health > 0)
+               {
+                       WaypointSprite_Kill(head.sprite);
+                       if(head.weaponentity) remove(head.weaponentity);
+                       if(head.iceblock) remove(head.iceblock);
+                       remove(head);
+               }
+               return 1;
+       }
+       
+       TD_count_alive_monsters();
+       
+       max_perteam = max_monsters * 0.5;
+       
+       if(time >= last_check)
+       if(total_killed < max_monsters)
+       {
+               if(redalive < max_perteam)
+                       SpawnMonsters(NUM_TEAM_1);
+               if(bluealive < max_perteam)
+                       SpawnMonsters(NUM_TEAM_2);
+                       
+               last_check = time + 0.5;
+       }
+               
+       if(total_killed < max_monsters)
+               return 0;
+       
+       if(TD_ALIVE_TEAMS_OK())
+               return 0;
+
+       float winner_team = TD_GetWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
+       }
+       
+       FOR_EACH_MONSTER(head) if(head.health > 0)
+       {
+               WaypointSprite_Kill(head.sprite);
+               if(head.weaponentity) remove(head.weaponentity);
+               if(head.iceblock) remove(head.iceblock);
+               remove(head);
+       }
+
+       round_handler_Init(5, 10, 180);
+       return 1;
+}
+
+float TD_CheckTeams()
+{
+       entity head;
+       float readycount = 0, num_players = 0, ready_needed_factor, ready_needed_count;
+       
+       FOR_EACH_REALPLAYER(head)
+       {
+               ++num_players;
+               if(head.ready)
+                       ++readycount;
+       }
+       
+       ready_needed_factor = bound(0.5, cvar("g_td_majority_factor"), 0.999);
+       ready_needed_count = floor(num_players * ready_needed_factor) + 1;
+       
+       if(readycount >= ready_needed_count || time >= ready_timeout)
+               return TRUE;
+       
+       allowed_to_spawn = TRUE;
+       
+       return FALSE;
+}
+
+// spawnfuncs  
+void spawnfunc_td_generator()
+{
+       if not(g_td) { remove(self); return; }
+       if not(self.team)
+       {
+               td_debug("Generator without a team, removing it.\n");
+               remove(self);
+               return;
+       }
+       
+       precache_sound("onslaught/generator_underattack.wav");
+       precache_sound("onslaught/ons_hit1.wav");
+       precache_sound("onslaught/ons_hit2.wav");
+       precache_sound("weapons/rocket_impact.wav");
+       
+       if not(self.health)
+               self.health = 1000;
+               
+       self.max_health = self.health;
+       self.classname = "td_generator";
+       self.flags = FL_GENERATOR;
+       
+       setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+       
+       setorigin(self, self.origin + '0 0 20');
+       droptofloor();
+       
+       InitializeEntity(self, td_generator_delayed, INITPRIO_LAST);
+}
+
+void spawnfunc_td_waypoint()
+{
+       if not(g_td) { remove(self); return; }
+       
+       setsize(self, '-6 -6 -6', '6 6 6');
+       
+       if not(self.noalign)
+       {
+               setorigin(self, self.origin + '0 0 20');
+               droptofloor();
+       }
+       
+       self.classname = "td_waypoint";
+       self.think = td_waypoint_think;
+       self.nextthink = time + 0.1;
+}
+
+void spawnfunc_td_controller()
+{
+       if not(g_td) { remove(self); return; }
+}
+
+void spawnfunc_td_spawnpoint()
+{
+       if not(g_td) { remove(self); return; }
+       
+       self.classname = "td_spawnpoint";
+       
+       self.effects = EF_STARDUST;
+}
+
+// initialization stuff
+void td_ScoreRules()
+{
+       ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, TRUE);
+       ScoreInfo_SetLabel_TeamScore(ST_TD_DESTROYS,  "destroyed", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_TD_DESTROYS,"destroyed", SFL_SORT_PRIO_PRIMARY);
+       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)
+       {
+               td_debug("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);
+       
+       readyrestart_happened = TRUE; // disable normal ready command
+       
+       ready_timeout = time + 30;
+       
+       round_handler_Spawn(TD_CheckTeams, TD_CheckWinner, TD_RoundStart);
+       round_handler_Init(5, 10, 180);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(td_TurretSpawn)
+{
+       if(self.realowner == world)
+               return TRUE;
+               
+       if(self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)
+               self.target_range = 500;
+               
+       self.bot_attack = FALSE;
+       buffturret(self, 0.7);
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
+{
+       if(!self.team || !self.realowner)
+       {
+               td_debug(strcat("Removed monster ", self.netname, " with team ", ftos(self.team), "\n"));
+               WaypointSprite_Kill(self.sprite);
+               if(self.weaponentity) remove(self.weaponentity);
+               remove(self);
+               return FALSE;
+       }
+       
+       self.candrop = FALSE;
+       self.bot_attack = FALSE;
+       self.ammo_fuel = bound(20, 20 * self.level, 100);
+       self.target_range = 300;
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterDies)
+{
+       vector backuporigin;
+       entity oldself;
+       
+       if(IS_PLAYER(frag_attacker.realowner))
+       {
+               PlayerScore_Add(frag_attacker.realowner, SP_SCORE, 5);
+               PlayerScore_Add(frag_attacker.realowner, SP_KILLS, 1);
+       }
+       
+       total_killed++;
+       
+       backuporigin = self.origin;
+       oldself = self;
+       self = spawn();
+       
+       self.gravity = 1;
+       setorigin(self, backuporigin + '0 0 5');
+       spawn_td_fuel(oldself.ammo_fuel);
+       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_MonsterThink)
+{
+       if(time <= game_starttime && round_handler_IsActive())
+               return TRUE;
+               
+       if(IS_PLAYER(self.enemy))
+               self.enemy = world;
+               
+       float tr = 100;
+       
+       if not(self.enemy)
+       if(monster_target.flags & FL_GENERATOR)
+       if(monster_target.health <= 0)
+               tr = 250;
+
+       if not(self.enemy) // don't change targets while attacking
+       if(vlen(monster_target.origin - self.origin) <= tr)
+       {
+               if(monster_target.target2)
+               {
+                       if(random() > 0.5)
+                               self.target2 = monster_target.target2;
+                       else
+                               self.target2 = monster_target.target;
+               }
+               else
+                       self.target2 = monster_target.target;
+                               
+               monster_target = find(world, targetname, self.target2);
+               
+               if(monster_target == world)
+                       monster_target = PickGenerator(self.team);
+                       
+               if(monster_target == world)
+                       return TRUE; // no generators or waypoints?!
+       }
+       
+       td_debug(sprintf("Monster name: %s. Monster target: %s. Monster target2: %s. Monster target entity: %s.\n", self.netname, self.target, self.target2, etos(monster_target)));
+       
+       if(!self.enemy && !monster_target)
+               return TRUE; // no enemy or target, must be wandering
+       
+       monster_speed_run = (150 + random() * 4) * monster_skill;
+       monster_speed_walk = (100 + random() * 4) * monster_skill;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
+{
+       entity e;
+       
+       for(e = world;(e = findflags(e, monster_attack, TRUE)); ) 
+       {
+               if(ignore_turrets)
+               if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+                       continue;
+                       
+               if(e.flags & FL_MONSTER)
+                       continue; // don't attack other monsters?
+               
+               if(monster_isvalidtarget(e, self))
+                       self.enemy = e;
+       }
+       
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
+{
+       self.monster_attack = FALSE;
+       self.bot_attack = FALSE;
+       self.solid = SOLID_CORPSE;
+       
+       if(self.newfuel)
+       {
+               self.ammo_fuel = self.newfuel;
+               self.newfuel = 0;
+       }
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_Damage)
+{
+       if(IS_PLAYER(frag_attacker))
+       if(frag_target.flags & FL_MONSTER || frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+               frag_damage = 0;
+               
+       if(IS_PLAYER(frag_attacker) || frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+       if(IS_PLAYER(frag_target))
+       {
+               frag_damage = 0;
+               if(frag_attacker != frag_target)
+                       frag_force = '0 0 0';
+       }
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerCommand)
+{
+       if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
+       
+       vector org;
+       
+       makevectors(self.v_angle);
+       
+       org = self.origin + self.view_ofs + v_forward * 100;
+       
+       tracebox(self.origin + self.view_ofs, '-16 -16 -16', '16 16 16', org, MOVE_NORMAL, self);
+       entity targ = trace_ent;
+       if(targ.owner.realowner == self)
+               targ = targ.owner;
+               
+       if(cmd_name == "ready")
+       if not(self.ready)
+       {
+               self.ready = TRUE;
+               bprint(self.netname, "^2 is ready\n");
+               
+               Nagger_ReadyCounted();
+               
+               return TRUE;
+       }
+       
+       if(cmd_name == "turretspawn")
+       {
+               if(argv(1) == "list")
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_LIST, "mlrs walker plasma towerbuff flac");
+                       return TRUE;
+               }
+               if(!IS_PLAYER(self) || self.health <= 0)
+               { 
+                       Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_CANTSPAWN);
+                       return TRUE;
+               }
+               if(max_turrets <= 0)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_DISABLED);
+                       return TRUE;
+               }
+               if(self.turret_cnt >= max_turrets)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXTURRETS, max_turrets);
+                       return TRUE;
+               }
+               
+               spawnturret(self, self, argv(1), trace_endpos);
+               
+               return TRUE;
+       }
+       if(cmd_name == "turretremove")
+       {
+               if((targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (targ.playerid == self.playerid || targ.realowner == self))
+               {
+                       self.turret_cnt -= 1;
+                       Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_REMOVE);
+                       WaypointSprite_Kill(targ.sprite);
+                       remove(targ.tur_head);
+                       remove(targ);
+                       return TRUE;
+               }
+               Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_REMOVE);
+               return TRUE;
+       }
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_ClientConnect)
+{
+       self.newfuel = 75;
+       
+       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; // nice try
+       }
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_DisableVehicles)
+{
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_SetModname)
+{
+       g_cloaked = 1;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
+{
+       if(time < game_starttime || (time <= game_starttime && round_handler_IsActive()) || gameover)
+       {
+               turret_target = world;
+               return FALSE; // battle hasn't started
+       }
+
+       if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
+    if(turret_target.flags & FL_PROJECTILE)
+       if(turret_target.owner.flags & FL_MONSTER)
+        return TRUE; // flac support
+       
+       if not(turret_target.flags & FL_MONSTER)
+               turret_target = world;
+               
+       if(!IsDifferentTeam(turret_target, turret))
+               turret_target = world;
+               
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretDies)
+{
+       if(self.realowner)
+               self.realowner.turret_cnt -= 1;
+                       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_GetTeamCount)
+{
+       ret_float = 2;
+       
+       return FALSE;
+}
+
+MUTATOR_DEFINITION(gamemode_towerdefense)
+{
+       MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterMove, td_MonsterThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDamage_Calculate, td_Damage, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
+       MUTATOR_HOOK(ClientConnect, td_ClientConnect, CBC_ORDER_ANY);
+       MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SetModname, td_SetModname, CBC_ORDER_ANY);
+       MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
+       MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetTeamCount, td_GetTeamCount, 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..a37958f
--- /dev/null
@@ -0,0 +1,30 @@
+float FL_GENERATOR = 2048;
+
+float ignore_turrets;
+
+#define SP_TD_DESTROYS 4
+#define ST_TD_DESTROYS 1
+
+float redalive, bluealive, total_alive;
+
+var float max_monsters = 20;
+var float max_alive = 10;
+
+float max_perteam;
+
+float total_killed;
+
+var float max_turrets = 10;
+
+.float turret_buff;
+
+.float newfuel; // hack to not give players fuel every time they spawn
+
+float last_check;
+
+.float turret_cnt;
+
+.float level;
+.float last_trace;
+
+float ready_timeout;
index a0786b7dabfee8c9369eef9f591cf33467075cd4..d6001d7286922a2a417e351ac8a891e7d486a93a 100644 (file)
@@ -109,6 +109,22 @@ MUTATOR_HOOKFUNCTION(minstagib_MatchEnd)
        return FALSE;
 }
 
+MUTATOR_HOOKFUNCTION(minstagib_MonsterLoot)
+{
+       other.monster_loot = spawnfunc_item_minst_cells;
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(minstagib_MonsterSpawn)
+{
+       // always refill ammo
+       if(self.monsterid == MONSTER_MAGE)
+               self.skin = 1;
+       
+       return FALSE;
+}
+
 MUTATOR_HOOKFUNCTION(minstagib_BotShouldAttack)
 {
        if(checkentity.items & IT_STRENGTH)
@@ -406,6 +422,8 @@ MUTATOR_HOOKFUNCTION(minstagib_BuildMutatorsPrettyString)
 MUTATOR_DEFINITION(mutator_minstagib)
 {
        MUTATOR_HOOK(MatchEnd, minstagib_MatchEnd, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterDropItem, minstagib_MonsterLoot, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterSpawn, minstagib_MonsterSpawn, CBC_ORDER_ANY);
        MUTATOR_HOOK(BotShouldAttack, minstagib_BotShouldAttack, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerPhysics, minstagib_PlayerPhysics, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerSpawn, minstagib_PlayerSpawn, CBC_ORDER_ANY);
index 3f9f020affc086c1968fbe80a2bfc64bd9a3d5e3..808d91c14ab3d737912d257bf0245f4efc161f94 100644 (file)
@@ -7,6 +7,7 @@ MUTATOR_DECLARATION(gamemode_keepaway);
 MUTATOR_DECLARATION(gamemode_ctf);
 MUTATOR_DECLARATION(gamemode_nexball);
 MUTATOR_DECLARATION(gamemode_onslaught);
+MUTATOR_DECLARATION(gamemode_towerdefense);
 MUTATOR_DECLARATION(gamemode_domination);
 MUTATOR_DECLARATION(gamemode_lms);
 
index 367e8609ded2741ab10a74503dbf7888a82bd1a6..bea1f8784258ddb03593f016efcc39b5909c2fa1 100644 (file)
@@ -46,11 +46,16 @@ mutators/gamemode_keepaway.qh
 mutators/gamemode_nexball.qh 
 mutators/gamemode_lms.qh
 mutators/mutator_dodging.qh
+mutators/gamemode_towerdefense.qh
 
 //// tZork Turrets ////
 tturrets/include/turrets_early.qh
 vehicles/vehicles_def.qh
 
+monsters/lib/monsters_early.qh
+
+generator.qh
+
 campaign.qh
 ../common/campaign_common.qh
 ../common/mapinfo.qh
@@ -204,6 +209,8 @@ spawnpoints.qc
 
 portals.qc
 
+generator.qc
+
 target_spawn.qc
 func_breakable.qc
 target_music.qc
@@ -225,6 +232,8 @@ round_handler.qc
 
 ../common/explosion_equation.qc
 
+monsters/monsters.qh
+
 mutators/base.qc
 mutators/gamemode_assault.qc
 mutators/gamemode_arena.qc
@@ -236,6 +245,7 @@ mutators/gamemode_keyhunt.qc
 mutators/gamemode_keepaway.qc
 mutators/gamemode_nexball.qc
 mutators/gamemode_onslaught.qc
+mutators/gamemode_towerdefense.qc
 mutators/gamemode_lms.qc
 mutators/mutator_invincibleproj.qc
 mutators/mutator_new_toys.qc
index 538dcc62c624b173caa59b1ff53686a39dd50109..5c80117b9718d4cca51fda73640e22ed293642a1 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 9a290a2cc3ed83830e77ea67c8abca0740a88787..b290c465599be0df777f4b873f02fe1435312a49 100644 (file)
@@ -684,6 +684,8 @@ void Item_Touch (void)
 
        if not(IS_PLAYER(other))
                return;
+       if (other.frozen)
+               return;
        if (other.deadflag)
                return;
        if (self.solid != SOLID_TRIGGER)
index ebb89aa5c9c0bf274742139a99d56f3ecf2eacb2..55029775454e3b4202539ee60b5e70d91aeb8985 100644 (file)
@@ -216,7 +216,8 @@ void target_spawn_edit_entity(entity e, string msg, entity kt, entity t2, entity
                        self = e;
                        activator = act;
 
-                       self.target_spawn_spawnfunc();
+                       if(self.target_spawn_spawnfunc)
+                               self.target_spawn_spawnfunc();
 
                        self = oldself;
                        activator = oldactivator;
index 0ba1e7c8481db848019a0f856fdf70ee236e0b32..ba8cdee26fc56b38e1406d29ef17b7690340bbf2 100644 (file)
@@ -105,6 +105,14 @@ void InitGameplayMode()
                MUTATOR_ADD(gamemode_ctf);
                have_team_spawns = -1; // request team spawns
        }
+    
+       if(g_td)
+       {
+               ActivateTeamplay();
+               leadlimit_override = 0; // not supported by TD
+               timelimit_override = 0;
+               MUTATOR_ADD(gamemode_towerdefense);
+       }
 
        if(g_lms)
        {
index 51fe9dfe7eb1b6df9594af2b8993c365b4c49eaa..d561862d0f2412e77f4f55f851d969f4ef1cbded 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 48a457a573dd04e3048cd8e1c11e1c4691a535a7..b5dfba6a9b7c891659a22ec902c513ba25502615 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;
@@ -1102,6 +1110,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;
@@ -1334,7 +1345,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 539be2ad9e1b38257bcd66f247bf1e68c9803d45..223f8eacf388f64a8e88d3d9ed08bf378a57b720 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) && IS_CLIENT(_target))
+    if ((_turret.target_select_playerbias > 0) && IS_CLIENT(_target) && !g_td)
+        p_score = 1;
+        
+    if(g_td && _target.flags & FL_MONSTER)
         p_score = 1;
 
     d_score = max(d_score, 0);
index 8118b8f234dfa1cb23d2c52d737489679dd47ab1..d1bf0893192e03bf755bae18f8568c819c142d9b 100644 (file)
@@ -7,6 +7,11 @@ 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 heal monsters in tower defense
+       {
+        self.enemy.health = min(self.enemy.health + self.shot_dmg,self.enemy.max_health);
+               self.enemy.SendFlags |= MSF_STATUS;
+       }
     fl_org = 0.5 * (self.enemy.absmin + self.enemy.absmax);
     te_smallflash(fl_org);
 }
@@ -38,15 +43,27 @@ 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.enemy.health >= self.enemy.max_health)
+            return 0;
+                       
+               if(IsDifferentTeam(self.enemy, self))
+                       return 0;
+                       
+               if(self.enemy.flags & FL_MONSTER)
+                       return 1; // the following checks are for turrets only
+    }
+       
        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 1e78bd5a28eec14fc7cffa9f5e9703ccffd09bc6..5dfaf0f0955cf292a7ec23ca4b37d812d0c73bcf 100644 (file)
@@ -564,6 +564,9 @@ void vehicles_touch()
 
     if(other.deadflag != DEAD_NO)
         return;
+               
+       if(other.frozen)
+               return;
 
     if(other.vehicle != world)
         return;
@@ -583,6 +586,9 @@ void vehicles_enter()
 
     if(self.phase > time)
         return;
+               
+       if(other.frozen)
+               return;
 
     if(teamplay)
     if(self.team)
@@ -636,6 +642,7 @@ void vehicles_enter()
 
     self.team                 = self.owner.team;
     self.flags               -= FL_NOTARGET;
+       self.monster_attack               = TRUE;
     
     if (IS_REAL_CLIENT(other))
     {
@@ -813,6 +820,7 @@ void vehicles_exit(float eject)
     sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTN_NORM);
     _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;   
     _vehicle.phase = time + 1;
+       _vehicle.monster_attack = FALSE;
     
     _vehicle.vehicle_exit(eject);
     
index 649e72374ecc3754ffb6f29b84075854212739e2..3cd6f605e2ec4c6d2ec82c2414d9df38bc184e53 100644 (file)
@@ -263,7 +263,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.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/monsters.shader b/scripts/monsters.shader
new file mode 100644 (file)
index 0000000..dad8eab
--- /dev/null
@@ -0,0 +1,40 @@
+textures/spider/spidertex
+{
+       cull none
+
+       {
+               map textures/spidertex
+       }
+}
+textures/cerberus/cerberus_text
+{
+       cull none
+
+       {
+               map textures/cerberus
+       }
+}
+textures/berzerker_texture
+{
+       cull none
+
+       {
+               map textures/ogre
+       }
+}
+mage
+{
+       cull none
+
+       {
+               map textures/mage
+       }
+}
+slimeDiffuse
+{
+       {
+               map textures/slime
+               alphaFunc GT0
+               rgbGen Vertex
+       }
+}
\ No newline at end of file
index a73e7e2064a38d6966bb223a7b425037d24b8901..8082c886fdc7e67dbfc6767e028ac49ed2113475 100644 (file)
@@ -14,3 +14,5 @@ tree
 tuba
 turrets
 weapons
+barricade
+monsters
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
index e1dd7ea8436cc38d3f26b230914f166254ecc4ae..0213aab21dd3de09191378e68519ba0d9fce5fda 100644 (file)
Binary files a/textures/bloodyskull.jpg and b/textures/bloodyskull.jpg differ
diff --git a/textures/bloodyskull_pants.jpg b/textures/bloodyskull_pants.jpg
new file mode 100644 (file)
index 0000000..51850c1
Binary files /dev/null and b/textures/bloodyskull_pants.jpg differ
diff --git a/textures/bloodyskull_robot_pants.tga b/textures/bloodyskull_robot_pants.tga
new file mode 100644 (file)
index 0000000..17e92ff
Binary files /dev/null and b/textures/bloodyskull_robot_pants.tga differ
diff --git a/textures/cerberus.png b/textures/cerberus.png
new file mode 100644 (file)
index 0000000..3ac7c05
Binary files /dev/null and b/textures/cerberus.png differ
diff --git a/textures/cerberus_pants.png b/textures/cerberus_pants.png
new file mode 100644 (file)
index 0000000..d109711
Binary files /dev/null and b/textures/cerberus_pants.png differ
diff --git a/textures/mage.tga b/textures/mage.tga
new file mode 100644 (file)
index 0000000..55aadba
Binary files /dev/null and b/textures/mage.tga differ
diff --git a/textures/mage_glow.tga b/textures/mage_glow.tga
new file mode 100644 (file)
index 0000000..8498c46
Binary files /dev/null and b/textures/mage_glow.tga differ
diff --git a/textures/mage_pants.tga b/textures/mage_pants.tga
new file mode 100644 (file)
index 0000000..a3d2e13
Binary files /dev/null and b/textures/mage_pants.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
index e9a74e293743845b9ac7bcdcf8bddb46da49072c..b9c2ccc9fe3035f627d6fd8fc755c8d7e621ef85 100644 (file)
Binary files a/textures/meat.tga and b/textures/meat.tga differ
index 52c5246f2d4cd117b3fdd791be0704a594810e38..44ca7ac1e90d6b007655cd9fe45523636938bf74 100644 (file)
Binary files a/textures/meat_alien.tga and b/textures/meat_alien.tga differ
diff --git a/textures/meat_alien_pants.tga b/textures/meat_alien_pants.tga
new file mode 100644 (file)
index 0000000..de36704
Binary files /dev/null and b/textures/meat_alien_pants.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
index 838f75d5f1440368376426478dd79cb1cd8dc06f..489d5a5387e75ce78498cb19ebaa188c8331277a 100644 (file)
Binary files a/textures/meat_robot.tga and b/textures/meat_robot.tga differ
diff --git a/textures/meat_robot_pants.tga b/textures/meat_robot_pants.tga
new file mode 100644 (file)
index 0000000..5bc59da
Binary files /dev/null and b/textures/meat_robot_pants.tga differ
diff --git a/textures/ogre.png b/textures/ogre.png
new file mode 100644 (file)
index 0000000..4bb8be2
Binary files /dev/null and b/textures/ogre.png differ
diff --git a/textures/ogre_pants.png b/textures/ogre_pants.png
new file mode 100644 (file)
index 0000000..bf9665d
Binary files /dev/null and b/textures/ogre_pants.png differ
diff --git a/textures/slime.png b/textures/slime.png
new file mode 100644 (file)
index 0000000..cf755c9
Binary files /dev/null and b/textures/slime.png differ
diff --git a/textures/slime_norm.png b/textures/slime_norm.png
new file mode 100644 (file)
index 0000000..4553e8e
Binary files /dev/null and b/textures/slime_norm.png differ
diff --git a/textures/slime_pants.png b/textures/slime_pants.png
new file mode 100644 (file)
index 0000000..2b325ec
Binary files /dev/null and b/textures/slime_pants.png differ
diff --git a/textures/spidertex.tga b/textures/spidertex.tga
new file mode 100644 (file)
index 0000000..c1c40c0
Binary files /dev/null and b/textures/spidertex.tga differ
diff --git a/textures/spidertex_glow.tga b/textures/spidertex_glow.tga
new file mode 100644 (file)
index 0000000..8c7d3dc
Binary files /dev/null and b/textures/spidertex_glow.tga differ
diff --git a/textures/spidertex_pants.tga b/textures/spidertex_pants.tga
new file mode 100644 (file)
index 0000000..09263af
Binary files /dev/null and b/textures/spidertex_pants.tga differ