Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Sun, 15 Sep 2013 07:14:33 +0000 (17:14 +1000)
committerMario <mario.mario@y7mail.com>
Sun, 15 Sep 2013 07:14:33 +0000 (17:14 +1000)
141 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_0.sounds [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]
monster_zombie.cfg [deleted file]
monsters.cfg [new file with mode: 0644]
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/autocvars.qh
qcsrc/client/progs.src
qcsrc/client/projectile.qc
qcsrc/client/scoreboard.qc
qcsrc/client/waypointsprites.qc
qcsrc/common/command/generic.qc
qcsrc/common/constants.qh
qcsrc/common/deathtypes.qh
qcsrc/common/mapinfo.qh
qcsrc/common/monsters/all.qh [new file with mode: 0644]
qcsrc/common/monsters/cl_monsters.qc [new file with mode: 0644]
qcsrc/common/monsters/cl_monsters.qh [new file with mode: 0644]
qcsrc/common/monsters/config.qc [new file with mode: 0644]
qcsrc/common/monsters/config.qh [new file with mode: 0644]
qcsrc/common/monsters/monster/animus.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/bruiser.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/brute.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/cerberus.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/knight.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/mage.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/shambler.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/slime.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/spider.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/stingray.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/wyvern.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/zombie.qc [new file with mode: 0644]
qcsrc/common/monsters/monsters.qc [new file with mode: 0644]
qcsrc/common/monsters/monsters.qh [new file with mode: 0644]
qcsrc/common/monsters/spawn.qc [new file with mode: 0644]
qcsrc/common/monsters/spawn.qh [new file with mode: 0644]
qcsrc/common/monsters/sv_monsters.qc [new file with mode: 0644]
qcsrc/common/monsters/sv_monsters.qh [new file with mode: 0644]
qcsrc/common/notifications.qh
qcsrc/menu/classes.c
qcsrc/menu/progs.src
qcsrc/menu/xonotic/dialog_monstertools.c [new file with mode: 0644]
qcsrc/menu/xonotic/mainwindow.c
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/miscfunctions.qc
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_invasion.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_invasion.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_onslaught.qc
qcsrc/server/mutators/mutator_dodging.qc
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_main.qc
qcsrc/server/tturrets/units/unit_fusionreactor.qc
qcsrc/server/vehicles/vehicles.qc
qcsrc/server/w_electro.qc
qcsrc/server/w_shotgun.qc
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/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 dca2334..67f5f34 100644 (file)
@@ -56,6 +56,7 @@ alias who                  "qc_cmd_svcmd  who                  ${* ?}" // Displa
 // generic commands (across all programs)
 alias addtolist            "qc_cmd_svmenu addtolist            ${* ?}" // Add a string to a cvar
 alias dumpcommands         "qc_cmd_svmenu dumpcommands         ${* ?}" // Dump all commands on the program to *_cmd_dump.txt
+alias dumpmonsters         "qc_cmd_svmenu dumpmonsters         ${* ?}" // Dump all monsters into monsters_dump.txt
 alias dumpnotifs           "qc_cmd_svcl   dumpnotifs           ${* ?}" // Dump all notifications into notifications_dump.txt
 alias maplist              "qc_cmd_svmenu maplist              ${* ?}" // Automatic control of maplist
 alias nextframe            "qc_cmd_svmenu nextframe            ${* ?}" // Execute the given command next frame of this VM
@@ -103,6 +104,7 @@ alias menu_showhudexit "menu_cmd directmenu HUDExit"
 alias menu_showhudoptions "menu_cmd directpanelhudmenu ${* ?}"
 alias menu_showsandboxtools "menu_cmd directmenu SandboxTools"
 alias menu_showquitdialog "menu_cmd directmenu Quit"
+alias menu_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
@@ -155,6 +157,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
@@ -184,6 +189,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 28c1af6..a7614d9 100644 (file)
@@ -425,6 +425,10 @@ seta menu_sandbox_edit_material ""
 
 bind f7 menu_showsandboxtools
 
+seta menu_monsters_edit_spawn ""
+seta menu_monsters_edit_skin 0
+seta menu_monsters_edit_movetarget 1
+
 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"
 
@@ -1512,6 +1516,7 @@ exec crosshairs.cfg
 exec gamemodes.cfg
 exec mutators.cfg
 exec notifications.cfg
+exec monsters.cfg
 
 // load console command aliases and settings
 exec commands.cfg
index d069d5f..7825214 100644 (file)
@@ -7978,7 +7978,6 @@ alpha 190 190 180
 sizeincrease -80
 color 0xFFFFFF 0xFFFFFF
 
-
 // nade effects
 effect nade_blue
 trailspacing 1
index 665fa55..e333cf0 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_invasion
 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_invasion
 alias sv_hook_gamerestart
 alias sv_hook_gameend
 
@@ -82,6 +84,7 @@ seta g_keyhunt_point_leadlimit -1     "Keyhunt point lead limit overriding the mapin
 seta g_race_laps_limit -1      "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 
 
 // =================================
@@ -134,6 +137,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_invasion_respawn_waves 0
+set g_invasion_respawn_delay 0
+set g_invasion_weapon_stay 0
 
 
 // =======
@@ -218,6 +224,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"
@@ -418,3 +425,12 @@ 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)"
+
+// ==========
+//  invasion
+// ==========
+set g_invasion 0 "Invasion: survive against waves of monsters"
+set g_invasion_round_timelimit 120 "maximum time to kill all monsters"
+set g_invasion_warmup 10 "time between waves to prepare for battle"
+set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
+set g_invasion_zombies_only 0 "only spawn zombies"
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 3d56cdb..3d38a28 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_0.sounds b/models/monsters/zombie.dpm_0.sounds
new file mode 100644 (file)
index 0000000..7a7cff6
--- /dev/null
@@ -0,0 +1,8 @@
+//TAG: zombie
+death sound/monsters/zombie/death 0
+sight sound/monsters/zombie/sight 0
+//ranged sound/monsters/zombie/ranged 0
+//melee sound/monsters/zombie/melee 0
+//pain sound/monsters/zombie/pain 0
+spawn sound/monsters/zombie/spawn 0
+idle sound/monsters/zombie/idle 0
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/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..8fe16f3
--- /dev/null
@@ -0,0 +1,182 @@
+// {{{ #1: Brute
+set g_monster_brute_attack_chainsaw_damage 15
+set g_monster_brute_attack_grenade_damage 40
+set g_monster_brute_attack_grenade_edgedamage 20
+set g_monster_brute_attack_grenade_force 15
+set g_monster_brute_attack_grenade_radius 200
+set g_monster_brute_attack_grenade_speed 1400
+set g_monster_brute_attack_grenade_speed_up 150
+set g_monster_brute_attack_uzi_bullets 3
+set g_monster_brute_attack_uzi_chance 0.1
+set g_monster_brute_attack_uzi_damage 10
+set g_monster_brute_attack_uzi_force 5
+set g_monster_brute_health 300
+set g_monster_brute_speed_run 200
+set g_monster_brute_speed_stop 300
+set g_monster_brute_speed_walk 100
+// }}}
+// {{{ #2: Animus
+set g_monster_animus_attack_jump_damage 80
+set g_monster_animus_attack_melee_damage 45
+set g_monster_animus_health 150
+set g_monster_animus_speed_run 350
+set g_monster_animus_speed_stop 100
+set g_monster_animus_speed_walk 150
+// }}}
+// {{{ #3: Shambler
+set g_monster_shambler_attack_claw_damage 50
+set g_monster_shambler_attack_lightning_damage 15
+set g_monster_shambler_attack_smash_damage 50
+set g_monster_shambler_health 500
+set g_monster_shambler_speed_run 150
+set g_monster_shambler_speed_stop 300
+set g_monster_shambler_speed_walk 100
+// }}}
+// {{{ #4: Bruiser
+set g_monster_bruiser_attack_melee_damage 50
+set g_monster_bruiser_health 200
+set g_monster_bruiser_speed_run 360
+set g_monster_bruiser_speed_stop 50
+set g_monster_bruiser_speed_walk 40
+// }}}
+// {{{ #5: Wyvern
+set g_monster_wyvern_attack_fireball_damage 30
+set g_monster_wyvern_attack_fireball_damagetime 3
+set g_monster_wyvern_attack_fireball_edgedamage 20
+set g_monster_wyvern_attack_fireball_force 50
+set g_monster_wyvern_attack_fireball_radius 120
+set g_monster_wyvern_attack_fireball_speed 900
+set g_monster_wyvern_health 95
+set g_monster_wyvern_speed_run 120
+set g_monster_wyvern_speed_stop 300
+set g_monster_wyvern_speed_walk 40
+// }}}
+// {{{ #6: Cerberus
+set g_monster_cerberus_attack_bite_damage 30
+set g_monster_cerberus_attack_jump_damage 40
+set g_monster_cerberus_health 100
+set g_monster_cerberus_speed_run 250
+set g_monster_cerberus_speed_stop 50
+set g_monster_cerberus_speed_walk 60
+// }}}
+// {{{ #7: Slime
+set g_monster_slime_attack_explode_damage 250
+set g_monster_slime_health 80
+set g_monster_slime_speed_run 400
+set g_monster_slime_speed_stop 20
+set g_monster_slime_speed_walk 20
+// }}}
+// {{{ #8: Knight
+set g_monster_knight_attack_fireball_chance 0.3
+set g_monster_knight_attack_fireball_damage 30
+set g_monster_knight_attack_fireball_damagetime 1
+set g_monster_knight_attack_fireball_edgedamage 10
+set g_monster_knight_attack_fireball_force 50
+set g_monster_knight_attack_fireball_radius 70
+set g_monster_knight_attack_inferno_chance 0.4
+set g_monster_knight_attack_inferno_damage 40
+set g_monster_knight_attack_inferno_damagetime 3
+set g_monster_knight_attack_jump_chance 0.2
+set g_monster_knight_attack_jump_damage 25
+set g_monster_knight_attack_jump_distance 500
+set g_monster_knight_attack_melee_damage 20
+set g_monster_knight_attack_spike_chance 0.5
+set g_monster_knight_attack_spike_damage 20
+set g_monster_knight_attack_spike_edgedamage 10
+set g_monster_knight_attack_spike_force 5
+set g_monster_knight_attack_spike_radius 20
+set g_monster_knight_health 300
+set g_monster_knight_speed_run 150
+set g_monster_knight_speed_stop 100
+set g_monster_knight_speed_walk 75
+// }}}
+// {{{ #9: Stingray
+set g_monster_stingray_attack_bite_damage 25
+set g_monster_stingray_attack_bite_delay 0.5
+set g_monster_stingray_health 115
+set g_monster_stingray_speed_run 200
+set g_monster_stingray_speed_stop 10
+set g_monster_stingray_speed_walk 40
+// }}}
+// {{{ #10: Mage
+set g_monster_mage_attack_grenade_chance 30
+set g_monster_mage_attack_grenade_damage 25
+set g_monster_mage_attack_grenade_edgedamage 20
+set g_monster_mage_attack_grenade_force 170
+set g_monster_mage_attack_grenade_lifetime 5
+set g_monster_mage_attack_grenade_radius 100
+set g_monster_mage_attack_grenade_speed 150
+set g_monster_mage_attack_grenade_speed_up 95
+set g_monster_mage_attack_melee_damage 30
+set g_monster_mage_attack_melee_delay 0.7
+set g_monster_mage_attack_spike_accel 400
+set g_monster_mage_attack_spike_damage 30
+set g_monster_mage_attack_spike_decel 400
+set g_monster_mage_attack_spike_delay 2
+set g_monster_mage_attack_spike_radius 60
+set g_monster_mage_attack_spike_smart 1
+set g_monster_mage_attack_spike_smart_mindist 600
+set g_monster_mage_attack_spike_smart_trace_max 2500
+set g_monster_mage_attack_spike_smart_trace_min 1000
+set g_monster_mage_attack_spike_speed_max 370
+set g_monster_mage_attack_spike_turnrate 0.65
+set g_monster_mage_heal_allies 15
+set g_monster_mage_heal_delay 1.5
+set g_monster_mage_heal_minhealth 250
+set g_monster_mage_heal_range 200
+set g_monster_mage_heal_self 35
+set g_monster_mage_health 200
+set g_monster_mage_shield_blockpercent 0.8
+set g_monster_mage_shield_delay 7
+set g_monster_mage_shield_time 3
+set g_monster_mage_speed_run 75
+set g_monster_mage_speed_stop 50
+set g_monster_mage_speed_walk 50
+// }}}
+// {{{ #11: Zombie
+set g_monster_zombie_attack_leap_damage 50
+set g_monster_zombie_attack_leap_delay 1.5
+set g_monster_zombie_attack_leap_force 55
+set g_monster_zombie_attack_leap_speed 500
+set g_monster_zombie_attack_melee_damage 40
+set g_monster_zombie_attack_melee_delay 1.2
+set g_monster_zombie_health 150
+set g_monster_zombie_speed_run 400
+set g_monster_zombie_speed_stop 100
+set g_monster_zombie_speed_walk 150
+// }}}
+// {{{ #12: Spider
+set g_monster_spider_attack_bite_damage 35
+set g_monster_spider_attack_bite_delay 1.2
+set g_monster_spider_attack_type 0
+set g_monster_spider_attack_web_damagetime 2
+set g_monster_spider_attack_web_delay 1.5
+set g_monster_spider_attack_web_speed 1000
+set g_monster_spider_attack_web_speed_up 150
+set g_monster_spider_health 160
+set g_monster_spider_speed_run 400
+set g_monster_spider_speed_stop 100
+set g_monster_spider_speed_walk 150
+// }}}
+
+// {{{ Misc
+set g_monsters 1
+set g_monsters_think_delay 0.1
+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_drop_time 10
+set g_monsters_owners 1
+set g_monsters_teams 1
+set g_monsters_spawnshieldtime 2
+set g_monsters_typefrag 1
+set g_monsters_target_range 2000
+set g_monsters_target_infront 0
+set g_monsters_attack_range 120
+set g_monsters_respawn 1
+set g_monsters_respawn_delay 20
+set g_monsters_score_kill 1
+set g_monsters_max 20
+set g_monsters_max_perplayer 0
+set g_monsters_armor_blockpercent 0.5
+// }}}
index 8e36cc9..2b067ad 100644 (file)
@@ -104,6 +104,7 @@ void CSQC_Init(void)
 
        // needs to be done so early because of the constants they create
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+       CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
@@ -823,6 +824,7 @@ 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_MODEL: CSQCModel_Read(bIsNewEntity); break;
                case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break;  
                case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break;
index 46920a4..6f5b445 100644 (file)
@@ -994,7 +994,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.
@@ -1086,15 +1086,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)
index 8175695..241d84b 100644 (file)
@@ -169,6 +169,8 @@ float autocvar_g_waypointsprite_spam;
 float autocvar_g_waypointsprite_timealphaexponent;
 var float autocvar_g_waypointsprite_turrets = TRUE;
 var float autocvar_g_waypointsprite_turrets_maxdist = 5000;
+var float autocvar_g_waypointsprite_monsters = TRUE;
+var float autocvar_g_waypointsprite_monsters_maxdist = 2000;
 var float autocvar_hud_cursormode = TRUE;
 float autocvar_hud_colorflash_alpha;
 float autocvar_hud_configure_checkcollisions;
index 114f0a5..7e19b41 100644 (file)
@@ -29,6 +29,9 @@ Defs.qc
 ../common/animdecide.qh
 command/cl_cmd.qh
 
+../common/monsters/cl_monsters.qh
+../common/monsters/monsters.qh
+
 autocvars.qh
 
 ../common/notifications.qh // must be after autocvars
@@ -113,6 +116,9 @@ noise.qc
 ../common/urllib.qc
 command/cl_cmd.qc
 
+../common/monsters/cl_monsters.qc
+../common/monsters/monsters.qc
+
 ../warpzonelib/anglestransform.qc
 ../warpzonelib/mathlib.qc
 ../warpzonelib/common.qc
index b86c234..c61f07c 100644 (file)
@@ -313,6 +313,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 339c8b2..4ec4338 100644 (file)
@@ -1087,15 +1087,23 @@ 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_monsters_killed, stat_monsters_total;
+       float rows = 0;
        string val;
+       
+       // 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_monsters_total)
+               rows += 1;
 
        // if no rows, return
        if not(rows)
@@ -1115,10 +1123,20 @@ 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 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 a2ace4f..2ab1a58 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 4ca8b37..8a564b8 100644 (file)
@@ -227,6 +227,59 @@ void GenericCommand_dumpcommands(float request)
        }
 }
 
+void GenericCommand_dumpmonsters(float request)
+{
+       switch(request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       #ifdef SVQC
+                       mon_config_file = -1;
+                       mon_config_alsoprint = -1;
+                       string filename = argv(1);
+                       
+                       if(filename == "")
+                       {
+                               filename = "monsters_dump.cfg";
+                               mon_config_alsoprint = FALSE;
+                       }
+                       else if(filename == "-")
+                       {
+                               filename = "monsters_dump.cfg";
+                               mon_config_alsoprint = TRUE;
+                       }
+                       mon_config_file = fopen(filename, FILE_WRITE);
+                       
+                       if(mon_config_file >= 0)
+                       {
+                               Dump_Monster_Settings();
+                               print(sprintf("Dumping monsters... File located in ^2data/data/%s^7.\n", filename));
+                               fclose(mon_config_file);
+                               mon_config_file = -1;
+                               mon_config_alsoprint = -1;
+                       }
+                       else
+                       {
+                               print(sprintf("^1Error: ^7Could not open file '%s'!\n", filename));
+                       }
+                       #else
+                       print(_("Monsters dump command only works with sv_cmd.\n"));
+                       #endif
+                       return;
+               }
+                       
+               default:
+               case CMD_REQUEST_USAGE:
+               {
+                       print(strcat("\nUsage:^3 ", GetProgramCommandPrefix(), " dumpmonsters [filename]"));
+                       print("  Where 'filename' is the file to write (default is monsters_dump.cfg),\n");
+                       print("  if supplied with '-' output to console as well as default,\n");
+                       print("  if left blank, it will only write to default.\n");
+                       return;
+               }
+       }
+}
+
 void GenericCommand_dumpnotifs(float request)
 {
        switch(request)
@@ -584,6 +637,7 @@ void GenericCommand_(float request)
 #define GENERIC_COMMANDS(request,arguments,command) \
        GENERIC_COMMAND("addtolist", GenericCommand_addtolist(request, arguments), "Add a string to a cvar") \
        GENERIC_COMMAND("dumpcommands", GenericCommand_dumpcommands(request), "Dump all commands on the program to *_cmd_dump.txt") \
+       GENERIC_COMMAND("dumpmonsters", GenericCommand_dumpmonsters(request), "Dump all monsters into monsters_dump.cfg") \
        GENERIC_COMMAND("dumpnotifs", GenericCommand_dumpnotifs(request), "Dump all notifications into notifications_dump.txt") \
        GENERIC_COMMAND("maplist", GenericCommand_maplist(request, arguments), "Automatic control of maplist") \
        GENERIC_COMMAND("nextframe", GenericCommand_nextframe(request, arguments, command), "Execute the given command next frame of this VM") \
index b3dafaa..f40dfee 100644 (file)
@@ -101,6 +101,7 @@ 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 SPRITERULE_DEFAULT = 0;
 const float SPRITERULE_TEAMPLAY = 1;
@@ -183,6 +184,9 @@ const float STAT_ROUNDSTARTTIME = 73;
 const float STAT_WEAPONS2 = 74;
 const float STAT_WEAPONS3 = 75;
 
+const float STAT_MONSTERS_TOTAL = 76;
+const float STAT_MONSTERS_KILLED = 77;
+
 // mod stats (1xx)
 const float STAT_REDALIVE = 100;
 const float STAT_BLUEALIVE = 101;
@@ -358,6 +362,8 @@ const float PROJECTILE_WAKICANNON = 29;
 const float PROJECTILE_BUMBLE_GUN = 30;
 const float PROJECTILE_BUMBLE_BEAM = 31;
 
+float PROJECTILE_MAGE_SPIKE = 32;
+
 const float PROJECTILE_NADE_RED = 50;
 const float PROJECTILE_NADE_RED_BURN = 51;
 const float PROJECTILE_NADE_BLUE = 52;
index e17cd33..8ea53b5 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_NADE,                                   DEATH_SELF_NADE,                                        DEATH_MURDER_NADE,                         NORMAL_POS) \
        DEATHTYPE(DEATH_NOAMMO,                 DEATH_SELF_NOAMMO,                  NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_ROT,                    DEATH_SELF_ROT,                     NO_MSG,                        NORMAL_POS) \
@@ -88,6 +111,7 @@ DEATHTYPES
 #define DEATH_ISSPECIAL(t)            ((t) >= DEATH_SPECIAL_START)
 #define DEATH_ISVEHICLE(t)            ((t) >= DEATH_VHFIRST && (t) <= DEATH_VHLAST)
 #define DEATH_ISTURRET(t)             ((t) >= DEATH_TURRET_FIRST && (t) <= DEATH_TURRET_LAST)
+#define DEATH_ISMONSTER(t)                       ((t) >= DEATH_MONSTER_FIRST && (t) <= DEATH_MONSTER_LAST)
 #define DEATH_WEAPONOFWEAPONDEATH(t)  ((t) & DEATH_WEAPONMASK)
 #define DEATH_ISWEAPON(t,w)           (!DEATH_ISSPECIAL(t) && DEATH_WEAPONOFWEAPONDEATH(t) == (w))
 #define DEATH_WEAPONOF(t)             (DEATH_ISSPECIAL(t) ? 0 : DEATH_WEAPONOFWEAPONDEATH(t))
index db08fdf..d8b8470 100644 (file)
@@ -78,6 +78,9 @@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,"timelimit=20 pointli
 REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30")
 #define g_keepaway IS_GAMETYPE(KEEPAWAY)
 
+REGISTER_GAMETYPE(_("Invasion"),invasion,g_invasion,INVASION,"pointlimit=5")
+#define g_invasion IS_GAMETYPE(INVASION)
+
 const float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
 const float MAPINFO_FEATURE_VEHICLES      = 2;
 const float MAPINFO_FEATURE_TURRETS       = 4;
diff --git a/qcsrc/common/monsters/all.qh b/qcsrc/common/monsters/all.qh
new file mode 100644 (file)
index 0000000..0c4f0af
--- /dev/null
@@ -0,0 +1,12 @@
+#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"
diff --git a/qcsrc/common/monsters/cl_monsters.qc b/qcsrc/common/monsters/cl_monsters.qc
new file mode 100644 (file)
index 0000000..24326dd
--- /dev/null
@@ -0,0 +1,298 @@
+// =========================
+//  CSQC Monster Properties
+// =========================
+
+
+.vector glowmod;
+void monster_changeteam()
+{
+       self.glowmod = Team_ColorRGB(self.team - 1);
+
+       if(self.team)
+       {
+               self.teamradar_color = Team_ColorRGB(self.team - 1);
+               self.colormap = 1024 + (self.team - 1) * 17;
+       }
+       else
+       {
+               self.teamradar_color = '1 0 0';
+               self.colormap = 1024;
+       }
+}
+
+void monster_die()
+{
+       MON_ACTION(self.monsterid, MR_DEATH);
+
+       self.solid = SOLID_CORPSE;
+}
+
+void monster_draw2d()
+{
+       if(self.netname == "")
+               return;
+
+       if(!autocvar_g_waypointsprite_monsters)
+               return;
+
+       if(autocvar_cl_hidewaypoints)
+               return;
+               
+       if(self.solid == SOLID_CORPSE)
+               return; // re-spawning
+
+       if(self.health <= 0)
+               return;
+
+       float dist = vlen(self.origin - view_origin);
+       float t = (GetPlayerColor(player_localnum) + 1);
+
+       vector o;
+       string txt;
+
+       if(autocvar_cl_vehicles_hud_tactical)
+       if(dist < 10240 && t != self.team)
+       {
+               // TODO: Vehicle tactical hud
+               o = project_3d_to_2d(self.origin + '0 0 1' * ((self.maxs_z + self.mins_z) * 0.5));
+               if(o_z < 0
+               || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
+               || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
+               || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
+               || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
+                       return; // Dont draw wp's for monsters out of view
+               o_z = 0;
+               if(hud != HUD_NORMAL)
+               {
+                       switch(hud)
+                       {
+                               case HUD_SPIDERBOT:
+                               case HUD_WAKIZASHI:
+                               case HUD_RAPTOR:
+                               case HUD_BUMBLEBEE:
+                                       vector pz = drawgetimagesize("gfx/vehicles/vth-mover.tga") * 0.25;
+                                       drawpic(o - pz * 0.5, "gfx/vehicles/vth-mover.tga", pz , '1 1 1', 0.75, DRAWFLAG_NORMAL);
+                                       break;
+                       }
+               }
+       }
+
+       if(dist > self.maxdistance)
+               return;
+
+       string spriteimage = self.netname;
+       float a = self.alpha * autocvar_hud_panel_fg_alpha;
+       vector rgb = spritelookupcolor(spriteimage, self.teamradar_color);
+
+
+       if(self.maxdistance > waypointsprite_normdistance)
+               a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
+       else if(self.maxdistance > 0)
+               a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
+
+       if(rgb == '0 0 0')
+       {
+               self.teamradar_color = '1 0 0';
+               print(sprintf("WARNING: sprite of name %s has no color, using red so you notice it\n", spriteimage));
+       }
+
+       txt = self.netname;
+       if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
+               txt = _("Spam");
+       else
+               txt = spritelookuptext(spriteimage);
+
+       if(autocvar_g_waypointsprite_uppercase)
+               txt = strtoupper(txt);
+
+       if(a > 1)
+       {
+               rgb *= a;
+               a = 1;
+       }
+
+       if(a <= 0)
+               return;
+
+       rgb = fixrgbexcess(rgb);
+
+       o = project_3d_to_2d(self.origin + '0 0 1' * (self.maxs_z + 15));
+       if(o_z < 0
+       || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
+       || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
+       || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
+       || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
+               return; // Dont draw wp's for monsters out of view
+
+       o_z = 0;
+
+       float edgedistance_min, crosshairdistance;
+               edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
+       (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
+       (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
+       (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
+
+       float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
+
+       crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
+
+       t = waypointsprite_scale * vidscale;
+       a *= waypointsprite_alpha;
+
+       {
+               a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
+               t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
+       }
+       if (edgedistance_min < waypointsprite_edgefadedistance) {
+               a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
+               t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
+       }
+       if(crosshairdistance < waypointsprite_crosshairfadedistance) {
+               a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
+               t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
+       }
+
+       draw_beginBoldFont();
+       o = drawspritearrow(o, M_PI, rgb, a, SPRITE_ARROW_SCALE * t);
+       o = drawspritetext(o, M_PI, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
+       drawhealthbar(
+                       o,
+                       0,
+                       self.health / 255,
+                       '0 0 0',
+                       '0 0 0',
+                       0.5 * SPRITE_HEALTHBAR_WIDTH * t,
+                       0.5 * SPRITE_HEALTHBAR_HEIGHT * t,
+                       SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize,
+                       SPRITE_HEALTHBAR_BORDER * t,
+                       0,
+                       rgb,
+                       a * SPRITE_HEALTHBAR_BORDERALPHA,
+                       rgb,
+                       a * SPRITE_HEALTHBAR_HEALTHALPHA,
+                       DRAWFLAG_NORMAL
+                       );
+       draw_endBoldFont();
+}
+
+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()
+{
+       entity mon = get_monsterinfo(self.monsterid);
+
+       if(mon.spawnflags & MONSTER_SIZE_BROKEN)
+               self.scale = 1.3;
+
+       self.netname = M_NAME(self.monsterid);
+
+       setorigin(self, self.origin);
+       setmodel(self, mon.model);
+       setsize(self, mon.mins, mon.maxs);
+
+       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.gravity            = 1;
+       self.draw                       = monster_draw;
+       self.draw2d                     = monster_draw2d;
+       self.maxdistance        = autocvar_g_waypointsprite_monsters_maxdist;
+}
+
+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_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/common/monsters/cl_monsters.qh b/qcsrc/common/monsters/cl_monsters.qh
new file mode 100644 (file)
index 0000000..aa6fb41
--- /dev/null
@@ -0,0 +1 @@
+void ent_monster();
diff --git a/qcsrc/common/monsters/config.qc b/qcsrc/common/monsters/config.qc
new file mode 100644 (file)
index 0000000..a568db3
--- /dev/null
@@ -0,0 +1,62 @@
+// ==========================
+//  Monster Config Generator
+// ==========================
+
+void M_Config_Queue_Swap(float root, float child, entity pass)
+{
+       string oldroot = mon_config_queue[root];
+       mon_config_queue[root] = mon_config_queue[child];
+       mon_config_queue[child] = oldroot;
+}
+
+float M_Config_Queue_Compare(float root, float child, entity pass)
+{
+       float i, r, c;
+
+       for(i = 1; i <= 100; ++i)
+       {
+               r = str2chr(mon_config_queue[root], i);
+               c = str2chr(mon_config_queue[child], i);
+               if(r == c) { continue; }
+               else if(c > r) { return -1; }
+               else { return 1; }
+       }
+       
+       return 0;
+}
+
+void Dump_Monster_Settings(void)
+{
+       float i, x, totalsettings = 0;
+       for(i = MON_FIRST; i <= MON_LAST; ++i)
+       {
+               // step 1: clear the queue
+               MON_CONFIG_COUNT = 0;
+               for(x = 0; x <= MAX_MON_CONFIG; ++x)
+                       { mon_config_queue[x] = string_null; }
+
+               // step 2: build new queue
+               MON_ACTION(i, MR_CONFIG);
+
+               // step 3: sort queue
+               heapsort(MON_CONFIG_COUNT, M_Config_Queue_Swap, M_Config_Queue_Compare, world);
+               
+               // step 4: write queue
+               MON_CONFIG_WRITETOFILE(sprintf("// {{{ #%d: %s\n", i, M_NAME(i)))
+               for(x = 0; x <= MON_CONFIG_COUNT; ++x)
+                       { MON_CONFIG_WRITETOFILE(mon_config_queue[x]) }
+               MON_CONFIG_WRITETOFILE("// }}}\n")
+
+               // step 5: debug info
+               print(sprintf("#%d: %s: %d settings...\n", i, M_NAME(i), MON_CONFIG_COUNT));
+               totalsettings += MON_CONFIG_COUNT;
+       }
+
+       // clear queue now that we're finished
+       MON_CONFIG_COUNT = 0;
+       for(x = 0; x <= MAX_MON_CONFIG; ++x)
+               { mon_config_queue[x] = string_null; }
+
+       // extra information
+       print(sprintf("Totals: %d monsters, %d settings\n", (i - 1), totalsettings));
+}
diff --git a/qcsrc/common/monsters/config.qh b/qcsrc/common/monsters/config.qh
new file mode 100644 (file)
index 0000000..5a44c17
--- /dev/null
@@ -0,0 +1,29 @@
+// ==========================
+//  Monster Config Generator
+// ==========================
+
+void Dump_Monster_Settings(void);
+float mon_config_file;
+float mon_config_alsoprint;
+
+#define MAX_MON_CONFIG 256
+float MON_CONFIG_COUNT;
+string mon_config_queue[MAX_MON_CONFIG];
+
+#define MON_CONFIG_QUEUE(a) { \
+       mon_config_queue[MON_CONFIG_COUNT] = a; \
+       ++MON_CONFIG_COUNT; }
+
+#define MON_CONFIG_WRITETOFILE(a) { \
+       fputs(mon_config_file, a); \
+       if(mon_config_alsoprint) { print(a); } }
+
+#define MON_CONFIG_WRITE_CVARS(monster,name) \
+               { MON_CONFIG_QUEUE( \
+                       sprintf("set g_monster_%s_%s %g\n", #monster, #name, \
+                       cvar(sprintf("g_monster_%s_%s", #monster, #name)))) } \
+
+#define MON_CONFIG_SETTINGS(monsettings) \
+       #define MON_ADD_CVAR(monster,name) MON_CONFIG_WRITE_CVARS(monster,name) \
+       monsettings \
+       #undef MON_ADD_CVAR
diff --git a/qcsrc/common/monsters/monster/animus.qc b/qcsrc/common/monsters/monster/animus.qc
new file mode 100644 (file)
index 0000000..8d56e0f
--- /dev/null
@@ -0,0 +1,146 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ ANIMUS,
+/* function   */ m_animus,
+/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_MELEE,
+/* mins,maxs  */ '-41 -41 -31', '41 41 31',
+/* model      */ "demon.mdl",
+/* netname    */ "animus",
+/* fullname   */ _("Animus")
+);
+
+#define ANIMUS_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_jump_damage) \
+       MON_ADD_CVAR(monster, attack_melee_damage) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+ANIMUS_SETTINGS(animus)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_touch_jump()
+{
+       if (self.health <= 0)
+               return;
+
+       if (monster_isvalidtarget(other, self))
+       {
+               if (vlen(self.velocity) > 300)
+               {
+                       Damage(other, self, self, MON_CVAR(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:
+               {
+                       return monster_melee(self.enemy, MON_CVAR(animus, attack_melee_damage), animus_anim_attack, self.attack_range, 1, DEATH_MONSTER_ANIMUS, TRUE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       return monster_leap(animus_anim_leap, animus_touch_jump, v_forward * 700 + '0 0 300', 0.8);
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_animus()
+{
+       self.classname = "monster_animus";
+       
+       self.monster_spawnfunc = spawnfunc_monster_animus;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_ANIMUS, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_demon1() { spawnfunc_monster_animus(); }
+void spawnfunc_monster_demon() { spawnfunc_monster_animus(); }
+
+float m_animus(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(animus, speed_run), MON_CVAR(animus, speed_walk), MON_CVAR(animus, speed_stop), animus_anim_run, animus_anim_walk, animus_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(animus_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(animus, health);
+                       
+                       self.monster_loot = spawnfunc_item_health_medium;
+                       self.monster_attackfunc = animus_attack;
+                       monsters_setframe(animus_anim_stand);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(ANIMUS_SETTINGS(animus))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_animus(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/demon.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/bruiser.qc b/qcsrc/common/monsters/monster/bruiser.qc
new file mode 100644 (file)
index 0000000..3ab609c
--- /dev/null
@@ -0,0 +1,138 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ BRUISER,
+/* function   */ m_bruiser,
+/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_MELEE,
+/* mins,maxs  */ '-20 -20 -31', '20 20 53',
+/* model      */ "knight.mdl",
+/* netname    */ "bruiser",
+/* fullname   */ _("Bruiser")
+);
+
+#define BRUISER_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_melee_damage) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+BRUISER_SETTINGS(bruiser)
+#endif // SVQC
+#else
+#ifdef SVQC
+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;
+
+float bruiser_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float len = vlen(self.velocity);
+                       
+                       return monster_melee(self.enemy, MON_CVAR(bruiser, attack_melee_damage), ((len < 50) ? bruiser_anim_attack : bruiser_anim_runattack), self.attack_range, 1.25, DEATH_MONSTER_BRUISER, FALSE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       // no ranged attacks for bruiser
+                       return FALSE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_bruiser()
+{
+       self.classname = "monster_bruiser";
+       
+       self.monster_spawnfunc = spawnfunc_monster_bruiser;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_BRUISER, FALSE)) { remove(self); return; }
+}
+
+float m_bruiser(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       entity pet = world;
+                       pet = findentity(pet, monster_owner, self);
+                       float rspeed = MON_CVAR(bruiser, speed_run);
+                       
+                       if(pet)
+                       if(self.enemy)
+                       if(vlen(self.enemy.origin - pet.origin) < vlen(self.enemy.origin - self.origin))
+                               rspeed = 0;
+                               
+                       monster_move(rspeed, MON_CVAR(bruiser, speed_walk), MON_CVAR(bruiser, speed_stop), bruiser_anim_run, bruiser_anim_walk, bruiser_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe((random() > 0.5) ? bruiser_anim_death1 : bruiser_anim_death2);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(bruiser, health);
+                       
+                       self.monster_loot = spawnfunc_item_armor_medium;
+                       self.monster_attackfunc = bruiser_attack;
+                       monsters_setframe(bruiser_anim_stand);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(BRUISER_SETTINGS(bruiser))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_bruiser(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/knight.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/brute.qc b/qcsrc/common/monsters/monster/brute.qc
new file mode 100644 (file)
index 0000000..91a307b
--- /dev/null
@@ -0,0 +1,270 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ BRUTE,
+/* function   */ m_brute,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs  */ '-36 -36 -20', '36 36 50',
+/* model      */ "ogre.dpm",
+/* netname    */ "brute",
+/* fullname   */ _("Brute")
+);
+
+#define BRUTE_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_chainsaw_damage) \
+       MON_ADD_CVAR(monster, attack_uzi_bullets) \
+       MON_ADD_CVAR(monster, attack_uzi_damage) \
+       MON_ADD_CVAR(monster, attack_uzi_force) \
+       MON_ADD_CVAR(monster, attack_uzi_chance) \
+       MON_ADD_CVAR(monster, attack_grenade_damage) \
+       MON_ADD_CVAR(monster, attack_grenade_edgedamage) \
+       MON_ADD_CVAR(monster, attack_grenade_force) \
+       MON_ADD_CVAR(monster, attack_grenade_radius) \
+       MON_ADD_CVAR(monster, attack_grenade_speed) \
+       MON_ADD_CVAR(monster, attack_grenade_speed_up) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+BRUTE_SETTINGS(brute)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_blade()
+{
+       self.brute_cycles += 1;
+       self.angles_y = self.angles_y + random()* 25;
+       
+       monster_melee(self.enemy, MON_CVAR(brute, attack_chainsaw_damage), brute_anim_swing, self.attack_range, 0, 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);
+       
+       sound(self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM);
+       fireBallisticBullet(CENTER_OR_VIEWOFS(self), v_forward, 0.02, 18000, 5, MON_CVAR(brute, attack_uzi_damage), MON_CVAR(brute, attack_uzi_force), DEATH_MONSTER_BRUTE_UZI, 0, 1, 115);
+       endFireBallisticBullet();
+       
+       if(self.brute_cycles <= MON_CVAR(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, ATTEN_NORM);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage (self, self.realowner, MON_CVAR(brute, attack_grenade_damage), MON_CVAR(brute, attack_grenade_edgedamage), MON_CVAR(brute, attack_grenade_radius), world, MON_CVAR(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;
+       
+       monster_makevectors(self.enemy);
+       
+       sound(self, CH_WEAPON_A, "weapons/grenade_fire.wav", VOL_BASE, ATTEN_NORM);
+
+       gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = MON_CVAR(brute, attack_grenade_damage);
+       gren.movetype = MOVETYPE_BOUNCE;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE;
+       setorigin(gren, CENTER_OR_VIEWOFS(self));
+       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 = 50;
+       gren.damageforcescale = 0;
+       gren.event_damage = brute_grenade_damage;
+       gren.damagedbycontents = TRUE;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SetupProjectileVelocityEx(gren, v_forward, v_up, MON_CVAR(brute, attack_grenade_speed), MON_CVAR(brute, attack_grenade_speed_up), 0, 0, FALSE);
+
+       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;
+                       self.attack_finished_single = time + 1.3;
+                       brute_blade();
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       self.brute_cycles = 0;
+                       if(random() <= MON_CVAR(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 spawnfunc_monster_brute()
+{
+       self.classname = "monster_brute";
+       
+       self.monster_spawnfunc = spawnfunc_monster_brute;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_BRUTE, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_ogre() { spawnfunc_monster_brute(); }
+
+float m_brute(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(brute, speed_run), MON_CVAR(brute, speed_walk), MON_CVAR(brute, speed_stop), brute_anim_run, brute_anim_walk, brute_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(brute_anim_die);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(brute, health);
+                       
+                       self.monster_loot = spawnfunc_item_bullets;
+                       self.monster_attackfunc = brute_attack;
+                       monsters_setframe(brute_anim_idle);
+                       self.weapon = WEP_GRENADE_LAUNCHER;
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_sound ("weapons/uzi_fire.wav");
+                       precache_sound ("weapons/grenade_impact.wav");
+                       precache_sound ("weapons/grenade_fire.wav");
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(BRUTE_SETTINGS(brute))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_brute(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/ogre.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/cerberus.qc b/qcsrc/common/monsters/monster/cerberus.qc
new file mode 100644 (file)
index 0000000..fabaa5a
--- /dev/null
@@ -0,0 +1,186 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ CERBERUS,
+/* function   */ m_cerberus,
+/* spawnflags */ MON_FLAG_MELEE,
+/* mins,maxs  */ '-16 -16 -24', '16 16 12',
+/* model      */ "dog.dpm",
+/* netname    */ "cerberus",
+/* fullname   */ _("Cerberus")
+);
+
+#define CERBERUS_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_bite_damage) \
+       MON_ADD_CVAR(monster, attack_jump_damage) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+CERBERUS_SETTINGS(cerberus)
+#endif // SVQC
+#else
+#ifdef SVQC
+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;
+
+.float cerberus_last_trace;
+
+void cerberus_findowner()
+{
+       if(time < self.cerberus_last_trace || self.monster_owner)
+               return;
+               
+       entity head;
+       
+       FOR_EACH_MONSTER(head)
+       if(head.health > 0)
+       if(head.monsterid == MON_BRUISER)
+       if(findentity(world, monster_owner, head) == world)
+       if(vlen(head.origin - self.origin) < self.target_range)
+       if(SAME_TEAM(head, self))
+       if(head.enemy == world)
+       {
+               self.monster_owner = head;
+               break;
+       }
+               
+       self.cerberus_last_trace = time + 3;
+}
+
+void cerberus_checkowner()
+{
+       if(time < self.cerberus_last_trace)
+               return;
+       if(IS_PLAYER(self.monster_owner))
+               return; // don't check player masters
+
+       if(vlen(self.origin - self.monster_owner.origin) > self.target_range)
+               self.monster_owner = world;
+       if(self.monster_owner.health < 1)
+               self.monster_owner = world;
+       if(DIFF_TEAM(self.monster_owner, self))
+               self.monster_owner = world;
+               
+       self.cerberus_last_trace = time + 3;
+}
+
+void cerberus_touch_jump()
+{
+       if (other.takedamage)
+       if (vlen(self.velocity) > 300)
+       {
+               Damage(self.enemy, self, self, MON_CVAR(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:
+               {
+                       return monster_melee(self.enemy, MON_CVAR(cerberus, attack_bite_damage), cerberus_anim_attack, self.attack_range, 0.7, DEATH_MONSTER_CERBERUS_BITE, TRUE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       return monster_leap(cerberus_anim_attack, cerberus_touch_jump, v_forward * 300 + '0 0 200', 0.8);
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_cerberus()
+{
+       self.classname = "monster_cerberus";
+       
+       self.monster_spawnfunc = spawnfunc_monster_cerberus;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_CERBERUS, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_dog() { spawnfunc_monster_cerberus(); }
+
+float m_cerberus(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       if(self.monster_owner)
+                               cerberus_checkowner();
+                       else
+                               cerberus_findowner();
+                       monster_move(MON_CVAR(cerberus, speed_run), MON_CVAR(cerberus, speed_walk), MON_CVAR(cerberus, speed_stop), cerberus_anim_run, cerberus_anim_walk, cerberus_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       if(self.monster_owner.flags & FL_MONSTER)
+                               self.monster_owner = world;
+                       monsters_setframe(cerberus_anim_die);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(cerberus, health);
+                       
+                       self.monster_loot = spawnfunc_item_health_small;
+                       self.monster_attackfunc = cerberus_attack;
+                       monsters_setframe(cerberus_anim_idle);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(CERBERUS_SETTINGS(cerberus))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_cerberus(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/dog.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
\ No newline at end of file
diff --git a/qcsrc/common/monsters/monster/knight.qc b/qcsrc/common/monsters/monster/knight.qc
new file mode 100644 (file)
index 0000000..37dee43
--- /dev/null
@@ -0,0 +1,339 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ KNIGHT,
+/* function   */ m_knight,
+/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs  */ '-20 -20 -32', '20 20 41',
+/* model      */ "hknight.mdl",
+/* netname    */ "knight",
+/* fullname   */ _("Knight")
+);
+
+#define KNIGHT_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_melee_damage) \
+       MON_ADD_CVAR(monster, attack_inferno_damage) \
+       MON_ADD_CVAR(monster, attack_inferno_damagetime) \
+       MON_ADD_CVAR(monster, attack_inferno_chance) \
+       MON_ADD_CVAR(monster, attack_fireball_damage) \
+       MON_ADD_CVAR(monster, attack_fireball_edgedamage) \
+       MON_ADD_CVAR(monster, attack_fireball_damagetime) \
+       MON_ADD_CVAR(monster, attack_fireball_force) \
+       MON_ADD_CVAR(monster, attack_fireball_radius) \
+       MON_ADD_CVAR(monster, attack_fireball_chance) \
+       MON_ADD_CVAR(monster, attack_spike_damage) \
+       MON_ADD_CVAR(monster, attack_spike_edgedamage) \
+       MON_ADD_CVAR(monster, attack_spike_force) \
+       MON_ADD_CVAR(monster, attack_spike_radius) \
+       MON_ADD_CVAR(monster, attack_spike_chance) \
+       MON_ADD_CVAR(monster, attack_jump_damage) \
+       MON_ADD_CVAR(monster, attack_jump_distance) \
+       MON_ADD_CVAR(monster, attack_jump_chance) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+KNIGHT_SETTINGS(knight)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_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, ATTEN_NORM);
+       
+       if(vlen(self.enemy.origin - self.origin) <= 2000)
+               Fire_AddDamage(self.enemy, self, MON_CVAR(knight, attack_inferno_damage) * monster_skill, MON_CVAR(knight, attack_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, MON_CVAR(knight, attack_fireball_damage), MON_CVAR(knight, attack_fireball_edgedamage), MON_CVAR(knight, attack_fireball_force), world, MON_CVAR(knight, attack_fireball_radius), self.projectiledeathtype, world);
+               
+               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= MON_CVAR(knight, attack_inferno_damage))
+                       Fire_AddDamage(e, self, 5 * monster_skill, MON_CVAR(knight, attack_fireball_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, ATTEN_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, MON_CVAR(knight, attack_spike_damage), MON_CVAR(knight, attack_spike_edgedamage), MON_CVAR(knight, attack_spike_force), world, MON_CVAR(knight, attack_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, "", MON_CVAR(knight, attack_fireball_chance), 1);
+       RandomSelection_Add(world, 2, "", MON_CVAR(knight, attack_inferno_chance), 1);
+       RandomSelection_Add(world, 3, "", MON_CVAR(knight, attack_spike_chance), 1);
+       if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > MON_CVAR(knight, attack_jump_distance)) ? 1 : MON_CVAR(knight, attack_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, MON_CVAR(knight, attack_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;
+                               
+                       return monster_melee(self.enemy, MON_CVAR(knight, attack_melee_damage), anim, self.attack_range, 0.7, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       return knight_attack_ranged();
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_knight()
+{
+       self.classname = "monster_knight";
+       
+       self.monster_spawnfunc = spawnfunc_monster_knight;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_KNIGHT, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); }
+
+float m_knight(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(knight, speed_run), MON_CVAR(knight, speed_walk), MON_CVAR(knight, speed_stop), knight_anim_run, knight_anim_walk, knight_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       float chance = random();
+                       monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
+                       if(chance < 0.10 || self.spawnflags & 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;
+                       }
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(knight, health);
+                       
+                       self.monster_loot = spawnfunc_item_armor_big;
+                       self.monster_attackfunc = knight_attack;
+                       monsters_setframe(knight_anim_stand);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_sound ("player/lava.wav");
+                       precache_sound ("weapons/fireball2.wav");
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(KNIGHT_SETTINGS(knight))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_knight(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/hknight.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc
new file mode 100644 (file)
index 0000000..fdba5c0
--- /dev/null
@@ -0,0 +1,488 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ MAGE,
+/* function   */ m_mage,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs  */ '-36 -36 -24', '36 36 50',
+/* model      */ "mage.dpm",
+/* netname    */ "mage",
+/* fullname   */ _("Mage")
+);
+
+#define MAGE_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_spike_damage) \
+       MON_ADD_CVAR(monster, attack_spike_radius) \
+       MON_ADD_CVAR(monster, attack_spike_delay) \
+       MON_ADD_CVAR(monster, attack_spike_accel) \
+       MON_ADD_CVAR(monster, attack_spike_decel) \
+       MON_ADD_CVAR(monster, attack_spike_turnrate) \
+       MON_ADD_CVAR(monster, attack_spike_speed_max) \
+       MON_ADD_CVAR(monster, attack_spike_smart) \
+       MON_ADD_CVAR(monster, attack_spike_smart_trace_min) \
+       MON_ADD_CVAR(monster, attack_spike_smart_trace_max) \
+       MON_ADD_CVAR(monster, attack_spike_smart_mindist) \
+       MON_ADD_CVAR(monster, attack_melee_damage) \
+       MON_ADD_CVAR(monster, attack_melee_delay) \
+       MON_ADD_CVAR(monster, attack_grenade_damage) \
+       MON_ADD_CVAR(monster, attack_grenade_edgedamage) \
+       MON_ADD_CVAR(monster, attack_grenade_force) \
+       MON_ADD_CVAR(monster, attack_grenade_radius) \
+       MON_ADD_CVAR(monster, attack_grenade_lifetime) \
+       MON_ADD_CVAR(monster, attack_grenade_chance) \
+       MON_ADD_CVAR(monster, attack_grenade_speed) \
+       MON_ADD_CVAR(monster, attack_grenade_speed_up) \
+       MON_ADD_CVAR(monster, heal_self) \
+       MON_ADD_CVAR(monster, heal_allies) \
+       MON_ADD_CVAR(monster, heal_minhealth) \
+       MON_ADD_CVAR(monster, heal_range) \
+       MON_ADD_CVAR(monster, heal_delay) \
+       MON_ADD_CVAR(monster, shield_time) \
+       MON_ADD_CVAR(monster, shield_delay) \
+       MON_ADD_CVAR(monster, shield_blockpercent) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+MAGE_SETTINGS(mage)
+#endif // SVQC
+#else
+#ifdef SVQC
+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;
+
+float friend_needshelp(entity e)
+{
+       if(e == world)
+               return FALSE;
+       if(e.health <= 0)
+               return FALSE;
+       if(vlen(e.origin - self.origin) > MON_CVAR(mage, heal_range))
+               return FALSE;
+       if(DIFF_TEAM(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: return (e.health < autocvar_g_balance_health_regenstable);
+               case 1: return ((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));
+               case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable);
+               case 3: return (e.health > 0);
+       }
+       
+       return FALSE;
+}
+
+void mageattack_melee()
+{
+       monster_melee(self.enemy, MON_CVAR(mage, attack_melee_damage), mage_anim_attack, self.attack_range, MON_CVAR(mage, attack_melee_delay) - 0.2, 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, ATTEN_NORM);
+       RadiusDamage (self, self.realowner, MON_CVAR(mage, attack_grenade_damage), MON_CVAR(mage, attack_grenade_edgedamage), MON_CVAR(mage, attack_grenade_radius), world, MON_CVAR(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);
+       
+       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, CENTER_OR_VIEWOFS(self));
+       setsize(gren, '-64 -64 -64', '64 64 64');
+
+       gren.nextthink = time + MON_CVAR(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_SetupProjectileVelocityEx(gren, v_forward, v_up, MON_CVAR(mage, attack_grenade_speed), MON_CVAR(mage, attack_grenade_speed_up), 0, 0, FALSE);
+       
+       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;
+       
+       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
+       
+       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+       RadiusDamage (self, self.realowner, MON_CVAR(mage, attack_spike_damage), MON_CVAR(mage, attack_spike_damage) * 0.5, MON_CVAR(mage, attack_spike_radius), world, 0, DEATH_MONSTER_MAGE, other);
+
+       remove (self);
+}
+
+void mage_spike_touch()
+{
+       PROJECTILE_TOUCH;
+
+       mage_spike_explode();
+}
+
+// copied from W_Seeker_Think
+void mage_spike_think()
+{
+       entity e;
+       vector desireddir, olddir, newdir, eorg;
+       float turnrate;
+       float dist;
+       float spd;
+
+       if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
+       {
+               self.projectiledeathtype |= HITTYPE_SPLASH;
+               mage_spike_explode();
+       }
+
+       spd = vlen(self.velocity);
+       spd = bound(
+               spd - MON_CVAR(mage, attack_spike_decel) * frametime,
+               MON_CVAR(mage, attack_spike_speed_max),
+               spd + MON_CVAR(mage, attack_spike_accel) * frametime
+       );
+
+       if (self.enemy != world)
+               if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+                       self.enemy = world;
+
+       if (self.enemy != world)
+       {
+               e               = self.enemy;
+               eorg            = 0.5 * (e.absmin + e.absmax);
+               turnrate        = MON_CVAR(mage, attack_spike_turnrate); // how fast to turn
+               desireddir      = normalize(eorg - self.origin);
+               olddir          = normalize(self.velocity); // get my current direction
+               dist            = vlen(eorg - self.origin);
+
+               // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
+               if (MON_CVAR(mage, attack_spike_smart) && (dist > MON_CVAR(mage, attack_spike_smart_mindist)))
+               {
+                       // Is it a better idea (shorter distance) to trace to the target itself?
+                       if ( vlen(self.origin + olddir * self.wait) < dist)
+                               traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
+                       else
+                               traceline(self.origin, eorg, FALSE, self);
+
+                       // Setup adaptive tracelength
+                       self.wait = bound(MON_CVAR(mage, attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = MON_CVAR(mage, attack_spike_smart_trace_max));
+
+                       // Calc how important it is that we turn and add this to the desierd (enemy) dir.
+                       desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
+               }
+               
+               newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
+               self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
+       }
+       else
+               dist = 0;
+               
+       ///////////////
+
+       //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
+       self.nextthink = time;// + 0.05; // csqc projectiles
+       UpdateCSQCProjectile(self);
+}
+
+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 + MON_CVAR(mage, heal_allies), 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 + MON_CVAR(mage, heal_allies), autocvar_g_balance_armor_regenstable);
+                                               fx = "armorrepair_fx";
+                                       }
+                                       break;
+                               case 3:
+                                       head.health = bound(0, head.health - ((head == self)  ? MON_CVAR(mage, heal_self) : MON_CVAR(mage, heal_allies)), 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 + MON_CVAR(mage, heal_allies), head.max_health);
+                       head.SendFlags |= MSF_STATUS;
+               }
+       }
+       
+       if(washealed)
+       {
+               monsters_setframe(mage_anim_attack);
+               self.attack_finished_single = time + MON_CVAR(mage, heal_delay);
+       }
+}
+
+void mage_shield_think()
+{
+       self.nextthink = time;
+
+       if(time >= self.ltime || self.owner.health <= 0)
+       {
+               self.owner.armorvalue = 0;
+               self.owner.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+               remove(self);
+               return;
+       }
+}
+
+void mage_shield()
+{
+       if(self.weaponentity)
+               return; // already have a shield
+               
+       entity shield = spawn();
+
+       shield.owner = self;
+       shield.team = self.team;
+       shield.ltime = time + MON_CVAR(mage, shield_time);
+       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;
+       shield.think = mage_shield_think;
+       shield.nextthink = time;
+       
+       setattachment(shield, self, "");
+       setmodel(shield, "models/ctf/shield.md3");
+       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+       
+       self.lastshielded = time + MON_CVAR(mage, shield_delay);
+       
+       monsters_setframe(mage_anim_attack);
+       self.attack_finished_single = time + 1;
+       
+       self.m_armor_blockpercent = MON_CVAR(mage, shield_blockpercent);
+       self.armorvalue = self.health;
+}
+
+float mage_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(mage_anim_attack);
+                       self.attack_finished_single = time + MON_CVAR(mage, attack_melee_delay);
+                       defer(0.2, mageattack_melee);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(random() < MON_CVAR(mage, attack_grenade_chance) / 100)
+                       {
+                               mage_throw_itemgrenade();
+                               return TRUE;
+                       }
+       
+                       monsters_setframe(mage_anim_attack);
+                       self.attack_finished_single = time + MON_CVAR(mage, attack_spike_delay);
+                       defer(0.2, mage_spike);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_mage()
+{
+       self.classname = "monster_mage";
+       
+       self.monster_spawnfunc = spawnfunc_monster_mage;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
+
+float m_mage(float req)
+{
+       switch(req)
+       {
+               case MR_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
+                       }
+                               
+                       if(self.health < MON_CVAR(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(MON_CVAR(mage, speed_run), MON_CVAR(mage, speed_walk), MON_CVAR(mage, speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(mage_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(mage, health);
+                       
+                       self.monster_loot = spawnfunc_item_health_large;
+                       self.monster_attackfunc = mage_attack;
+                       monsters_setframe(mage_anim_walk);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/items/g_h50.md3");
+                       precache_model ("models/ctf/shield.md3");
+                       precache_sound ("weapons/grenade_impact.wav");
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(MAGE_SETTINGS(mage))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_mage(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/mage.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/shambler.qc b/qcsrc/common/monsters/monster/shambler.qc
new file mode 100644 (file)
index 0000000..91d1e66
--- /dev/null
@@ -0,0 +1,182 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ SHAMBLER,
+/* function   */ m_shambler,
+/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs  */ '-41 -41 -31', '41 41 65',
+/* model      */ "shambler.mdl",
+/* netname    */ "shambler",
+/* fullname   */ _("Shambler")
+);
+
+#define SHAMBLER_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_smash_damage) \
+       MON_ADD_CVAR(monster, attack_claw_damage) \
+       MON_ADD_CVAR(monster, attack_lightning_damage) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+SHAMBLER_SETTINGS(shambler)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_smash()
+{
+       monster_melee(self.enemy, MON_CVAR(shambler, attack_smash_damage), shambler_anim_smash, self.attack_range, 0.4, 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);
+       monster_melee(self.enemy, MON_CVAR(shambler, attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, TRUE);
+       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, MON_CVAR(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 spawnfunc_monster_shambler()
+{
+       self.classname = "monster_shambler";
+       
+       self.monster_spawnfunc = spawnfunc_monster_shambler;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
+}
+
+float m_shambler(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(shambler, speed_run), MON_CVAR(shambler, speed_walk), MON_CVAR(shambler, speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(shambler_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(shambler, health);
+                       if not(self.attack_range) self.attack_range = 150;
+                       
+                       self.monster_loot = spawnfunc_item_health_mega;
+                       self.monster_attackfunc = shambler_attack;
+                       monsters_setframe(shambler_anim_stand);
+                       self.weapon = WEP_NEX;
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(SHAMBLER_SETTINGS(shambler))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_shambler(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/shambler.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/slime.qc b/qcsrc/common/monsters/monster/slime.qc
new file mode 100644 (file)
index 0000000..ffda3b9
--- /dev/null
@@ -0,0 +1,169 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ SLIME,
+/* function   */ m_slime,
+/* spawnflags */ 0,
+/* mins,maxs  */ '-16 -16 -24', '16 16 16',
+/* model      */ "slime.dpm",
+/* netname    */ "slime",
+/* fullname   */ _("Slime")
+);
+
+#define SLIME_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_explode_damage) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+SLIME_SETTINGS(slime)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_touch_jump()
+{
+       if(self.health > 0)
+       if(other.health > 0)
+       if(other.takedamage)
+       if(vlen(self.velocity) > 200)
+       {
+               Damage (self, world, world, MON_CVAR(slime, attack_explode_damage), 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);
+                       return monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5);
+               }
+       }
+       
+       return FALSE;
+}
+
+void slime_explode()
+{
+       RadiusDamage(self, self, MON_CVAR(slime, attack_explode_damage), 15, MON_CVAR(slime, attack_explode_damage) * 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, ATTEN_NORM);
+       
+       setmodel(self, "");
+}
+
+void slime_dead()
+{
+       self.health = -100; // gibbed
+       slime_explode();
+       
+       self.deadflag = DEAD_DEAD;
+       self.think = Monster_Fade;
+       self.nextthink = time + 0.1;
+}
+
+void spawnfunc_monster_slime()
+{
+       self.classname = "monster_slime";
+       
+       self.monster_spawnfunc = spawnfunc_monster_slime;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_SLIME, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); }
+
+float m_slime(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(slime, speed_run), MON_CVAR(slime, speed_walk), MON_CVAR(slime, speed_stop), slime_anim_walk, slime_anim_walk, slime_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       self.think                      = slime_dead;
+                       self.nextthink          = time;
+                       self.event_damage   = func_null;
+                       self.movetype           = MOVETYPE_NONE;
+                       self.takedamage         = DAMAGE_NO;
+                       self.enemy                      = world;
+                       self.health                     = 0;
+                       
+                       self.SendFlags |= MSF_MOVE | MSF_STATUS;
+                       
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(slime, health);
+                       
+                       self.monster_loot = spawnfunc_item_rockets;
+                       self.monster_attackfunc = slime_attack;
+                       monsters_setframe(slime_anim_idle);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_sound ("weapons/rocket_impact.wav");
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(SLIME_SETTINGS(slime))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_slime(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/slime.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/spider.qc b/qcsrc/common/monsters/monster/spider.qc
new file mode 100644 (file)
index 0000000..f2850c1
--- /dev/null
@@ -0,0 +1,241 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ SPIDER,
+/* function   */ m_spider,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs  */ '-18 -18 -25', '18 18 30',
+/* model      */ "spider.dpm",
+/* netname    */ "spider",
+/* fullname   */ _("Spider")
+);
+
+#define SPIDER_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_bite_damage) \
+       MON_ADD_CVAR(monster, attack_bite_delay) \
+       MON_ADD_CVAR(monster, attack_web_damagetime) \
+       MON_ADD_CVAR(monster, attack_web_speed) \
+       MON_ADD_CVAR(monster, attack_web_speed_up) \
+       MON_ADD_CVAR(monster, attack_web_delay) \
+       MON_ADD_CVAR(monster, attack_type) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+SPIDER_SETTINGS(spider)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_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 != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+               {
+                       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, MON_CVAR(spider, attack_web_damagetime), 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';
+       
+       monster_makevectors(self.enemy);
+       
+       sound(self, CH_SHOTS, snd, VOL_BASE, ATTEN_NORM);
+
+       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, CENTER_OR_VIEWOFS(self));
+
+       //proj.glow_size = 50;
+       //proj.glow_color = 45;
+       proj.movetype = MOVETYPE_BOUNCE;
+       W_SetupProjectileVelocityEx(proj, v_forward, v_up, MON_CVAR(spider, attack_web_speed), MON_CVAR(spider, attack_web_speed_up), 0, 0, FALSE);
+       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:
+               {       
+                       return monster_melee(self.enemy, MON_CVAR(spider, attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, MON_CVAR(spider, attack_bite_delay), DEATH_MONSTER_SPIDER, TRUE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(self.enemy.frozen)
+                               return FALSE;
+                       
+                       monsters_setframe(spider_anim_attack2);
+                       self.attack_finished_single = time + MON_CVAR(spider, attack_web_delay);
+                       spider_shootweb(self.spider_type);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_spider() 
+{
+       self.classname = "monster_spider";
+       
+       self.monster_spawnfunc = spawnfunc_monster_spider;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+}
+
+float m_spider(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(spider, speed_run), MON_CVAR(spider, speed_walk), MON_CVAR(spider, speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(spider_anim_attack);
+                       self.angles_x = 180;
+                       self.SendFlags |= MSF_ANG;
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(spider, health);
+                       if not(self.spider_type) self.spider_type = MON_CVAR(spider, attack_type);
+                       
+                       self.monster_loot = spawnfunc_item_health_medium;
+                       self.monster_attackfunc = spider_attack;
+                       monsters_setframe(spider_anim_idle);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/ice/ice.md3");
+                       precache_sound ("weapons/electro_fire2.wav");
+                       precache_sound ("weapons/fireball_fire.wav");
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(SPIDER_SETTINGS(spider))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_spider(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/spider.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/stingray.qc b/qcsrc/common/monsters/monster/stingray.qc
new file mode 100644 (file)
index 0000000..5a53356
--- /dev/null
@@ -0,0 +1,121 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ STINGRAY,
+/* function   */ m_stingray,
+/* spawnflags */ MONSTER_TYPE_SWIM | MONSTER_SIZE_BROKEN | MON_FLAG_MELEE,
+/* mins,maxs  */ '-20 -20 -31', '20 20 20',
+/* model      */ "fish.mdl",
+/* netname    */ "stingray",
+/* fullname   */ _("Stingray")
+);
+
+#define STINGRAY_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_bite_damage) \
+       MON_ADD_CVAR(monster, attack_bite_delay) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+STINGRAY_SETTINGS(stingray)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float stingray_anim_attack = 0;
+const float stingray_anim_death  = 1;
+const float stingray_anim_swim   = 2;
+const float stingray_anim_pain   = 3;
+
+float stingray_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       return monster_melee(self.enemy, MON_CVAR(stingray, attack_bite_damage), stingray_anim_attack, self.attack_range, MON_CVAR(stingray, attack_bite_delay), DEATH_MONSTER_STINGRAY, FALSE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       // no ranged attack for stingray (yet?)
+                       return FALSE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_stingray()
+{
+       self.classname = "monster_stingray";
+       
+       self.monster_spawnfunc = spawnfunc_monster_stingray;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_STINGRAY, TRUE)) { remove(self); return; }
+}
+
+float m_stingray(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(stingray, speed_run), MON_CVAR(stingray, speed_walk), MON_CVAR(stingray, speed_stop), stingray_anim_swim, stingray_anim_swim, stingray_anim_swim);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(stingray_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(stingray, health);
+                       
+                       self.monster_loot = spawnfunc_item_health_small;
+                       self.monster_attackfunc = stingray_attack;
+                       monsters_setframe(stingray_anim_swim);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(STINGRAY_SETTINGS(stingray))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_stingray(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/fish.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/wyvern.qc b/qcsrc/common/monsters/monster/wyvern.qc
new file mode 100644 (file)
index 0000000..0afc5f9
--- /dev/null
@@ -0,0 +1,178 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ WYVERN,
+/* function   */ m_wyvern,
+/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED,
+/* mins,maxs  */ '-20 -20 -58', '20 20 20',
+/* model      */ "wizard.mdl",
+/* netname    */ "wyvern",
+/* fullname   */ _("Wyvern")
+);
+
+#define WYVERN_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_fireball_damage) \
+       MON_ADD_CVAR(monster, attack_fireball_edgedamage) \
+       MON_ADD_CVAR(monster, attack_fireball_damagetime) \
+       MON_ADD_CVAR(monster, attack_fireball_force) \
+       MON_ADD_CVAR(monster, attack_fireball_radius) \
+       MON_ADD_CVAR(monster, attack_fireball_speed) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+WYVERN_SETTINGS(wyvern)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_fireball_explode()
+{
+       entity e;
+       if(self)
+       {
+               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage(self, self.realowner, MON_CVAR(wyvern, attack_fireball_damage), MON_CVAR(wyvern, attack_fireball_edgedamage), MON_CVAR(wyvern, attack_fireball_force), world, MON_CVAR(wyvern, attack_fireball_radius), self.projectiledeathtype, world);
+               
+               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= MON_CVAR(wyvern, attack_fireball_radius))
+                       Fire_AddDamage(e, self, 5 * monster_skill, MON_CVAR(wyvern, attack_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 * MON_CVAR(wyvern, attack_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 spawnfunc_monster_wyvern()
+{
+       self.classname = "monster_wyvern";
+       
+       self.monster_spawnfunc = spawnfunc_monster_wyvern;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
+
+float m_wyvern(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(wyvern, speed_run), MON_CVAR(wyvern, speed_walk), MON_CVAR(wyvern, speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(wyvern_anim_death);
+                       self.velocity_x = -200 + 400 * random();
+                       self.velocity_y = -200 + 400 * random();
+                       self.velocity_z = 100 + 100 * random();
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(wyvern, health);
+                       
+                       self.monster_loot = spawnfunc_item_cells;
+                       self.monster_attackfunc = wyvern_attack;
+                       monsters_setframe(wyvern_anim_hover);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(WYVERN_SETTINGS(wyvern))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_wyvern(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/wizard.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc
new file mode 100644 (file)
index 0000000..805fb7b
--- /dev/null
@@ -0,0 +1,216 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ ZOMBIE,
+/* function   */ m_zombie,
+/* spawnflags */ MON_FLAG_MELEE,
+/* mins,maxs  */ '-18 -18 -25', '18 18 47',
+/* model      */ "zombie.dpm",
+/* netname    */ "zombie",
+/* fullname   */ _("Zombie")
+);
+
+#define ZOMBIE_SETTINGS(monster) \
+       MON_ADD_CVAR(monster, health) \
+       MON_ADD_CVAR(monster, attack_melee_damage) \
+       MON_ADD_CVAR(monster, attack_melee_delay) \
+       MON_ADD_CVAR(monster, attack_leap_damage) \
+       MON_ADD_CVAR(monster, attack_leap_force) \
+       MON_ADD_CVAR(monster, attack_leap_speed) \
+       MON_ADD_CVAR(monster, attack_leap_delay) \
+       MON_ADD_CVAR(monster, speed_stop) \
+       MON_ADD_CVAR(monster, speed_run) \
+       MON_ADD_CVAR(monster, speed_walk) 
+
+#ifdef SVQC
+ZOMBIE_SETTINGS(zombie)
+#endif // SVQC
+#else
+#ifdef SVQC
+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_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) * MON_CVAR(zombie, attack_leap_force);
+               Damage(other, self, self, MON_CVAR(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;
+}
+
+void zombie_blockend()
+{
+       if(self.health <= 0)
+               return;
+
+       monsters_setframe(zombie_anim_blockend);
+       self.armorvalue = 0;
+       self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+}
+
+float zombie_block()
+{
+       monsters_setframe(zombie_anim_blockstart);
+       self.armorvalue = 100;
+       self.m_armor_blockpercent = 0.9;
+       self.state = MONSTER_STATE_ATTACK_MELEE; // freeze monster
+       self.attack_finished_single = time + 2.1;
+       
+       defer(2, zombie_blockend);
+       
+       return TRUE;
+}
+
+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;
+                       
+                       if(random() < 0.3 && self.health < 75 && self.enemy.health > 10)
+                               return zombie_block();
+                       
+                       return monster_melee(self.enemy, MON_CVAR(zombie, attack_melee_damage), chosen_anim, self.attack_range, MON_CVAR(zombie, attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       return monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * MON_CVAR(zombie, attack_leap_speed) + '0 0 200', MON_CVAR(zombie, attack_leap_delay));
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_zombie() 
+{
+       self.classname = "monster_zombie";
+       
+       self.monster_spawnfunc = spawnfunc_monster_zombie;
+       
+       self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+}
+
+float m_zombie(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(MON_CVAR(zombie, speed_run), MON_CVAR(zombie, speed_walk), MON_CVAR(zombie, speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       self.armorvalue = 0;
+                       self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+                       monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = MON_CVAR(zombie, health);
+                       
+                       if(self.spawnflags & MONSTERFLAG_NORESPAWN)
+                               self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
+                       
+                       self.monster_loot = spawnfunc_item_health_medium;
+                       self.monster_attackfunc = zombie_attack;
+                       monsters_setframe(zombie_anim_spawn);
+                       self.spawn_time = time + 2.1;
+                       self.spawnshieldtime = self.spawn_time;
+                       self.respawntime = 0.2;
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_CONFIG:
+               {
+                       MON_CONFIG_SETTINGS(ZOMBIE_SETTINGS(zombie))
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_zombie(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/zombie.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monsters.qc b/qcsrc/common/monsters/monsters.qc
new file mode 100644 (file)
index 0000000..9cc7211
--- /dev/null
@@ -0,0 +1,49 @@
+#include "all.qh"
+
+// MONSTER PLUGIN SYSTEM
+entity monster_info[MON_MAXCOUNT];
+entity dummy_monster_info;
+
+void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname)
+{
+       entity e;
+       monster_info[id - 1] = e = spawn();
+       e.classname = "monster_info";
+       e.monsterid = id;
+       e.netname = shortname;
+       e.monster_name = mname;
+       e.monster_func = func;
+       e.mdl = modelname;
+       e.spawnflags = monsterflags;
+       e.mins = min_s;
+       e.maxs = max_s;
+       e.model = strzone(strcat("models/monsters/", modelname));
+       
+       #ifndef MENUQC
+       func(MR_INIT);
+       #endif
+}
+float m_null(float dummy) { return 0; }
+void register_monsters_done()
+{
+       dummy_monster_info = spawn();
+       dummy_monster_info.classname = "monster_info";
+       dummy_monster_info.monsterid = 0; // you can recognize dummies by this
+       dummy_monster_info.netname = "";
+       dummy_monster_info.monster_name = "Monster";
+       dummy_monster_info.monster_func = m_null;
+       dummy_monster_info.mdl = "";
+       dummy_monster_info.mins = '-0 -0 -0';
+       dummy_monster_info.maxs = '0 0 0';
+       dummy_monster_info.model = "";
+}
+entity get_monsterinfo(float id)
+{
+       entity m;
+       if(id < MON_FIRST || id > MON_LAST)
+               return dummy_monster_info;
+       m = monster_info[id - 1];
+       if(m)
+               return m;
+       return dummy_monster_info;
+}
\ No newline at end of file
diff --git a/qcsrc/common/monsters/monsters.qh b/qcsrc/common/monsters/monsters.qh
new file mode 100644 (file)
index 0000000..d0f7a9a
--- /dev/null
@@ -0,0 +1,96 @@
+// monster requests
+#define MR_SETUP          1 // (SERVER) setup monster data
+#define MR_THINK                 2 // (SERVER) logic to run every frame
+#define MR_DEATH          3 // (BOTH) called when monster dies
+#define MR_INIT           4 // (BOTH) precaches models/sounds used by this monster
+#define MR_CONFIG         5 // (ALL)
+
+// functions:
+entity get_monsterinfo(float id);
+
+// special spawn flags
+const float MONSTER_RESPAWN_DEATHPOINT = 16; // re-spawn where we died
+const float MONSTER_TYPE_FLY = 32;
+const float MONSTER_TYPE_SWIM = 64;
+const float MONSTER_SIZE_BROKEN = 128; // TODO: remove when bad models are replaced
+const float MON_FLAG_SUPERMONSTER = 256; // incredibly powerful monster
+const float MON_FLAG_RANGED = 512; // monster shoots projectiles
+const float MON_FLAG_MELEE = 1024;
+
+// entity properties of monsterinfo:
+.float monsterid; // MON_...
+.string netname; // short name
+.string monster_name; // human readable name
+.float(float) monster_func; // m_...
+.string mdl; // currently a copy of the model
+.string model; // full name of model
+.float spawnflags;
+.vector mins, maxs; // monster hitbox size
+
+// csqc linking
+#ifndef MENUQC
+.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
+
+// other useful macros
+#define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest)
+#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name
+
+// =====================
+//  Monster Registration
+// =====================
+
+float m_null(float dummy);
+void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname);
+void register_monsters_done();
+
+const float MON_MAXCOUNT = 24;
+#define MON_FIRST 1
+float MON_COUNT;
+float MON_LAST;
+
+#define REGISTER_MONSTER_2(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+       float id; \
+       float func(float); \
+       void RegisterMonsters_##id() \
+       { \
+               MON_LAST = (id = MON_FIRST + MON_COUNT); \
+               ++MON_COUNT; \
+               register_monster(id,func,monsterflags,min_s,max_s,modelname,shortname,mname); \
+       } \
+       ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id)
+#ifdef MENUQC
+#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+       REGISTER_MONSTER_2(MON_##id,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
+#else
+#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+       REGISTER_MONSTER_2(MON_##id,func,monsterflags,min_s,max_s,modelname,shortname,mname)
+#endif
+
+#define MON_DUPECHECK(dupecheck,cvar) \
+       #ifndef dupecheck \
+               #define dupecheck \
+               float cvar; \
+       #else \
+               #error DUPLICATE MONSTER CVAR: cvar \
+       #endif
+
+#define MON_ADD_CVAR(monster,name) \
+               MON_DUPECHECK(MON_CVAR_##monster##_##name, autocvar_g_monster_##monster##_##name)
+
+#define MON_CVAR(monster,name) autocvar_g_monster_##monster##_##name
+
+#include "all.qh"
+
+#undef MON_ADD_CVAR
+#undef REGISTER_MONSTER
+ACCUMULATE_FUNCTION(RegisterMonsters, register_monsters_done)
diff --git a/qcsrc/common/monsters/spawn.qc b/qcsrc/common/monsters/spawn.qc
new file mode 100644 (file)
index 0000000..e8dfdd5
--- /dev/null
@@ -0,0 +1,62 @@
+entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+{
+       // ensure spawnfunc database is initialized
+       initialize_field_db();
+       
+       entity e = spawn();
+       
+       e.spawnflags = MONSTERFLAG_SPAWNED;
+       
+       if not(respwn)
+               e.spawnflags |= MONSTERFLAG_NORESPAWN;
+       
+       setorigin(e, orig);
+       
+       if(monster != "")
+       {
+               float i, found = 0;
+               entity mon;
+               for(i = MON_FIRST; i <= MON_LAST; ++i)
+               {
+                       mon = get_monsterinfo(i);
+                       if(mon.netname == monster)
+                       {
+                               found = TRUE;
+                               break;
+                       }
+               }
+               if not(found)
+                       monster = (get_monsterinfo(MON_FIRST)).netname;
+       }
+               
+       if(monster == "")
+       if(mnster)
+               monster = (get_monsterinfo(mnster)).netname;
+       
+       e.realowner = spawnedby;
+       
+       if(moveflag)
+               e.monster_moveflags = moveflag;
+       
+       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/common/monsters/spawn.qh b/qcsrc/common/monsters/spawn.qh
new file mode 100644 (file)
index 0000000..7d84103
--- /dev/null
@@ -0,0 +1 @@
+entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag);
diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc
new file mode 100644 (file)
index 0000000..f6856ed
--- /dev/null
@@ -0,0 +1,1128 @@
+// =========================
+//  SVQC Monster Properties
+// =========================
+
+
+void monster_item_spawn()
+{
+       if(self.monster_loot)
+               self.monster_loot();
+
+       self.gravity = 1;
+       self.velocity = randomvec() * 175 + '0 0 325';
+       self.classname = "droppedweapon"; // hax
+
+       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
+}
+
+void monster_dropitem()
+{
+       if(!self.candrop || !self.monster_loot)
+               return;
+
+       vector org = self.origin + ((self.mins + self.maxs) * 0.5);
+       entity e = spawn();
+
+       setorigin(e, org);
+
+       e.monster_loot = self.monster_loot;
+
+       other = e;
+       MUTATOR_CALLHOOK(MonsterDropItem);
+       e = other;
+
+       if(e)
+       {
+               e.think = monster_item_spawn;
+               e.nextthink = time + 0.3;
+       }
+}
+
+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(targ == ent)
+               return FALSE; // don't attack ourselves
+
+       traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+
+       if(trace_ent != targ)
+               return FALSE;
+
+       if(targ.vehicle_flags & VHF_ISVEHICLE)
+       if not((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED)
+               return FALSE; // melee attacks are useless against vehicles
+
+       if(time < game_starttime)
+               return FALSE; // monsters do nothing before the match has started
+
+       if(vlen(targ.origin - ent.origin) >= ent.target_range)
+               return FALSE; // enemy is too far away
+
+       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)
+       if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
+               return FALSE; // enemy/self is dead
+
+       if(ent.monster_owner == targ)
+               return FALSE; // don't attack our master
+
+       if(targ.monster_owner == ent)
+               return FALSE; // don't attack our pet
+
+       if not(targ.vehicle_flags & VHF_ISVEHICLE)
+       if(targ.flags & FL_NOTARGET)
+               return FALSE; // enemy can't be targeted
+
+       if not(autocvar_g_monsters_typefrag)
+       if(targ.BUTTON_CHAT)
+               return FALSE; // no typefragging!
+
+       if(SAME_TEAM(targ, ent))
+               return FALSE; // enemy is on our team
+
+       if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_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 head, closest_target = world;
+       head = findradius(ent.origin, ent.target_range);
+
+       while(head) // find the closest acceptable target to pass to
+       {
+               if(head.monster_attack)
+               if(monster_isvalidtarget(head, ent))
+               {
+                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+                       vector head_center = CENTER_OR_VIEWOFS(head);
+                       vector ent_center = CENTER_OR_VIEWOFS(ent);
+
+                       //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+                       if(closest_target)
+                       {
+                               vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+                               if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
+                                       { closest_target = head; }
+                       }
+                       else { closest_target = head; }
+               }
+
+               head = head.chain;
+       }
+
+       return closest_target;
+}
+
+void MonsterTouch ()
+{
+       if(other == world)
+               return;
+
+       if(self.enemy != other)
+       if not(other.flags & FL_MONSTER)
+       if(monster_isvalidtarget(other, self))
+               self.enemy = other;
+}
+
+string get_monster_model_datafilename(string m, float sk, string fil)
+{
+       if(m)
+               m = strcat(m, "_");
+       else
+               m = "models/monsters/*_";
+       if(sk >= 0)
+               m = strcat(m, ftos(sk));
+       else
+               m = strcat(m, "*");
+       return strcat(m, ".", fil);
+}
+
+void PrecacheMonsterSounds(string f)
+{
+       float fh;
+       string s;
+       fh = fopen(f, FILE_READ);
+       if(fh < 0)
+               return;
+       while((s = fgets(fh)))
+       {
+               if(tokenize_console(s) != 3)
+               {
+                       dprint("Invalid sound info line: ", s, "\n");
+                       continue;
+               }
+               PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
+       }
+       fclose(fh);
+}
+
+void precache_monstersounds()
+{
+       string m = (get_monsterinfo(self.monsterid)).model;
+       float globhandle, n, i;
+       string f;
+
+       globhandle = search_begin(strcat(m, "_*.sounds"), TRUE, FALSE);
+       if (globhandle < 0)
+               return;
+       n = search_getsize(globhandle);
+       for (i = 0; i < n; ++i)
+       {
+               //print(search_getfilename(globhandle, i), "\n");
+               f = search_getfilename(globhandle, i);
+               PrecacheMonsterSounds(f);
+       }
+       search_end(globhandle);
+}
+
+void ClearMonsterSounds()
+{
+#define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; }
+       ALLMONSTERSOUNDS
+#undef _MSOUND
+}
+
+.string GetMonsterSoundSampleField(string type)
+{
+       GetMonsterSoundSampleField_notFound = 0;
+       switch(type)
+       {
+#define _MSOUND(m) case #m: return monstersound_##m;
+               ALLMONSTERSOUNDS
+#undef _MSOUND
+       }
+       GetMonsterSoundSampleField_notFound = 1;
+       return string_null;
+}
+
+float LoadMonsterSounds(string f, float first)
+{
+       float fh;
+       string s;
+       var .string field;
+       fh = fopen(f, FILE_READ);
+       if(fh < 0)
+       {
+               dprint("Monster sound file not found: ", f, "\n");
+               return 0;
+       }
+       while((s = fgets(fh)))
+       {
+               if(tokenize_console(s) != 3)
+                       continue;
+               field = GetMonsterSoundSampleField(argv(0));
+               if(GetMonsterSoundSampleField_notFound)
+                       continue;
+               if(self.field)
+                       strunzone(self.field);
+               self.field = strzone(strcat(argv(1), " ", argv(2)));
+       }
+       fclose(fh);
+       return 1;
+}
+
+.float skin_for_monstersound;
+void UpdateMonsterSounds()
+{
+       entity mon = get_monsterinfo(self.monsterid);
+
+       if(self.skin == self.skin_for_monstersound)
+               return;
+       self.skin_for_monstersound = self.skin;
+       ClearMonsterSounds();
+       //LoadMonsterSounds("sound/monsters/default.sounds", 1);
+       if(!autocvar_g_debug_defaultsounds)
+       if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0))
+               LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0);
+}
+
+void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
+{
+       if(delaytoo && time < self.msound_delay)
+               return; // too early
+       GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
+
+       self.msound_delay = time + sound_delay;
+}
+
+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);
+}
+
+float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, float deathtype, float dostop)
+{
+       float rdmg = damg * random();
+
+       if (self.health <= 0)
+               return FALSE; // attacking while dead?!
+
+       if(dostop)
+       {
+               self.velocity_x = 0;
+               self.velocity_y = 0;
+               self.state = MONSTER_STATE_ATTACK_MELEE;
+               self.SendFlags |= MSF_MOVE;
+       }
+
+       monsters_setframe(anim);
+
+       if(anim_finished != 0)
+               self.attack_finished_single = time + anim_finished;
+
+       monster_makevectors(targ);
+
+       traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
+
+       if(trace_ent.takedamage)
+               Damage(trace_ent, self, self, rdmg * monster_skill, deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+
+       return TRUE;
+}
+
+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;
+               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;
+
+               self.SendFlags |= MSF_MOVE;
+               self.SendFlags |= MSF_STATUS;
+       }
+       else
+               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))
+       {
+               MonsterSound(monstersound_melee, 0, FALSE, CH_VOICE);
+               return;
+       }
+
+       if(vlen(targ.origin - e.origin) > e.attack_range)
+       if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
+       {
+               MonsterSound(monstersound_ranged, 0, FALSE, CH_VOICE);
+               return;
+       }
+}
+
+void monster_use ()
+{
+       if not(self.enemy)
+       if(self.health > 0)
+       if(monster_isvalidtarget(activator, self))
+               self.enemy = activator;
+}
+
+.float last_trace;
+.float last_enemycheck; // for checking enemy
+vector monster_pickmovetarget(entity targ)
+{
+       // enemy is always preferred target
+       if(self.enemy)
+       {
+               makevectors(self.angles);
+               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;
+                       return (self.monster_owner) ? self.monster_owner.origin : self.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 = rint(random() * 500);
+                       makevectors(self.angles);
+                       pos = self.origin + v_forward * 600;
+
+                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
+                       if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
+                       {
+                               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);
+
+               self.SendFlags |= MSF_STATUS;
+
+               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
+
+               // don't bother updating angles here?
+               if(self.origin != self.oldorigin)
+               {
+                       self.oldorigin = self.origin;
+                       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.fish_wasdrowning = TRUE;
+                               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 if(self.fish_wasdrowning)
+               {
+                       self.fish_wasdrowning = FALSE;
+                       self.angles_x = 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);
+               if(self.oldorigin != self.origin)
+               {
+                       self.oldorigin = self.origin;
+                       self.SendFlags |= MSF_MOVE;
+               }
+               return;
+       }
+
+       targ = monster_target;
+       runspeed = monster_speed_run;
+       walkspeed = monster_speed_walk;
+
+       if(teamplay)
+       if(autocvar_g_monsters_teams)
+       if(DIFF_TEAM(self.monster_owner, self))
+               self.monster_owner = world;
+
+       if(self.enemy && self.enemy.health < 1)
+               self.enemy = world; // enough!
+
+       if(time >= self.last_enemycheck)
+       {
+               if not(monster_isvalidtarget(self.enemy, self))
+                       self.enemy = world;
+
+               if not(self.enemy)
+               {
+                       self.enemy = FindTarget(self);
+                       if(self.enemy)
+                               MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
+               }
+
+               self.last_enemycheck = time + 0.5;
+       }
+
+       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)
+               MonsterSound(monstersound_idle, 5, TRUE, CH_VOICE);
+
+       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(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;
+
+       if(self.enemy && self.enemy.vehicle)
+               runspeed = 0;
+
+       if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
+               v_forward = normalize(self.moveto - self.origin);
+       else
+               self.moveto_z = self.origin_z;
+
+       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)
+               if(vlen(self.velocity) > 10)
+                       monsters_setframe((self.enemy) ? manim_run : manim_walk);
+               else
+                       monsters_setframe(manim_idle);
+       }
+       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);
+       }
+
+       monster_checkattack(self, self.enemy);
+
+       if(self.angles != self.oldangles)
+       {
+               self.oldangles = self.angles;
+               self.SendFlags |= MSF_ANG;
+       }
+
+       if(self.origin != self.oldorigin)
+       {
+               self.oldorigin = self.origin;
+               self.SendFlags |= MSF_MOVE;
+       }
+}
+
+void monster_dead_think()
+{
+       self.think = monster_dead_think;
+       self.nextthink = time + self.ticrate;
+
+       self.deadflag = DEAD_DEAD;
+
+       if(self.ltime != 0)
+       if(time >= self.ltime)
+       {
+               Monster_Fade();
+               return;
+       }
+
+       if(self.oldorigin != self.origin)
+       {
+               self.oldorigin = self.origin;
+               self.SendFlags |= MSF_MOVE;
+       }
+}
+
+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;
+
+       self.SendFlags |= MSF_STATUS;
+}
+
+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 monster_die()
+{
+       self.think = monster_dead_think;
+       self.nextthink = self.ticrate;
+       self.ltime = time + 5;
+
+       monster_dropitem();
+
+       MonsterSound(monstersound_death, 0, FALSE, CH_VOICE);
+
+       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))
+       if not(self.monster_respawned)
+               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
+       self.reset                      = func_null;
+       self.state                      = 0;
+       self.attack_finished_single = 0;
+
+       if not(self.flags & FL_FLY)
+               self.velocity = '0 0 0';
+
+       self.SendFlags |= MSF_MOVE;
+
+       // number of monsters spawned with mobspawn command
+       totalspawned -= 1;
+
+       MON_ACTION(self.monsterid, MR_DEATH);
+}
+
+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;
+
+       vector v;
+       float take, save;
+
+       v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, damage);
+       take = v_x;
+       save = v_y;
+
+       self.health -= take;
+
+       self.dmg_time = time;
+
+       if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
+               spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
+
+       self.velocity += force * self.damageforcescale;
+
+       if(deathtype != DEATH_DROWN)
+       {
+               Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker);
+               if (take > 50)
+                       Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
+               if (take > 100)
+                       Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
+       }
+
+       if(self.health <= 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
+
+               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;
+}
+
+void monster_think()
+{
+       self.think = monster_think;
+       self.nextthink = self.ticrate;
+
+       MON_ACTION(self.monsterid, MR_THINK);
+}
+
+void monster_spawn()
+{
+       MON_ACTION(self.monsterid, MR_SETUP);
+
+       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);
+
+       if not(self.attack_range)
+               self.attack_range = autocvar_g_monsters_attack_range;
+
+       self.pos1 = self.origin;
+
+       //monster_setupsounds(self.netname);
+       precache_monstersounds();
+       UpdateMonsterSounds();
+       //monster_precachesounds(self);
+
+       if(teamplay)
+               self.monster_attack = TRUE; // we can have monster enemies in team games
+               
+       MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
+
+       self.think = monster_think;
+       self.nextthink = time + self.ticrate;
+
+       self.SendFlags |= MSF_SETUP;
+
+       MUTATOR_CALLHOOK(MonsterSpawn);
+}
+
+float monster_initialize(float mon_id, float nodrop)
+{
+       if not(autocvar_g_monsters)
+               return FALSE;
+
+       entity mon = get_monsterinfo(mon_id);
+
+       // support for quake style removing monsters based on skill
+       switch(monster_skill)
+       {
+               case 0:
+               case 1: if(self.spawnflags & MONSTERSKILL_NOTEASY)              return FALSE; break;
+               case 2: if(self.spawnflags & MONSTERSKILL_NOTMEDIUM)    return FALSE; break;
+               default:
+               case 3: if(self.spawnflags & MONSTERSKILL_NOTHARD)              return FALSE; break;
+       }
+
+       if(self.team && !teamplay)
+               self.team = 0;
+
+       if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
+       if not(self.monster_respawned)
+               monsters_total += 1;
+
+       setsize(self, mon.mins, mon.maxs);
+       self.flags                              = FL_MONSTER;
+       self.takedamage                 = DAMAGE_AIM;
+       self.bot_attack                 = TRUE;
+       self.iscreature                 = TRUE;
+       self.teleportable               = TRUE;
+       self.damagedbycontents  = TRUE;
+       self.monsterid                  = mon_id;
+       self.damageforcescale   = 0;
+       self.event_damage               = monsters_damage;
+       self.touch                              = MonsterTouch;
+       self.use                                = monster_use;
+       self.solid                              = SOLID_BBOX;
+       self.movetype                   = MOVETYPE_WALK;
+       self.spawnshieldtime    = time + autocvar_g_monsters_spawnshieldtime;
+       self.enemy                              = world;
+       self.velocity                   = '0 0 0';
+       self.moveto                             = self.origin;
+       self.pos2                               = self.angles;
+       self.reset                              = monsters_reset;
+       self.netname                    = mon.netname;
+       self.monster_name               = M_NAME(mon_id);
+       self.candrop                    = TRUE;
+       self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
+       self.oldtarget2                 = self.target2;
+       self.deadflag                   = DEAD_NO;
+       self.scale                              = 1;
+       self.noalign                    = nodrop;
+       self.spawn_time                 = time;
+       self.gravity                    = 1;
+       self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+       if(mon.spawnflags & MONSTER_TYPE_SWIM)
+               self.flags |= FL_SWIM;
+
+       if(mon.spawnflags & MONSTER_TYPE_FLY)
+       {
+               self.flags |= FL_FLY;
+               self.movetype = MOVETYPE_FLY;
+       }
+
+       if(mon.spawnflags & MONSTER_SIZE_BROKEN)
+               self.scale = 1.3;
+
+       if not(self.ticrate)
+               self.ticrate = autocvar_g_monsters_think_delay;
+
+       self.ticrate = bound(sys_frametime, self.ticrate, 60);
+
+       if not(self.m_armor_blockpercent)
+               self.m_armor_blockpercent = 0.5;
+
+       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(monster_spawn);
+
+       return TRUE;
+}
diff --git a/qcsrc/common/monsters/sv_monsters.qh b/qcsrc/common/monsters/sv_monsters.qh
new file mode 100644 (file)
index 0000000..2327887
--- /dev/null
@@ -0,0 +1,78 @@
+.string spawnmob;
+.float monster_attack;
+
+float monster_skill;
+
+.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
+
+.float(float attack_type) monster_attackfunc;
+const float MONSTER_ATTACK_MELEE       = 1;
+const float MONSTER_ATTACK_RANGED      = 2;
+
+.float fish_wasdrowning; // used to reset a drowning fish's angles if it reaches water again
+
+.float candrop;
+
+.float attack_range;
+
+.float spawn_time; // stop monster from moving around right after spawning
+
+.string oldtarget2;
+.float lastshielded;
+
+.vector oldangles;
+
+.float m_armor_blockpercent;
+
+// monster sounds
+// copied from player sounds
+.float msound_delay; // temporary antilag system
+#define ALLMONSTERSOUNDS \
+               _MSOUND(death) \
+               _MSOUND(sight) \
+               _MSOUND(ranged) \
+               _MSOUND(melee) \
+               _MSOUND(pain) \
+               _MSOUND(spawn) \
+               _MSOUND(idle) 
+
+#define _MSOUND(m) .string monstersound_##m;
+ALLMONSTERSOUNDS
+#undef _MSOUND
+
+float GetMonsterSoundSampleField_notFound;
+
+.float monster_respawned; // used to make sure we're not recounting respawned monster stats
+
+const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1
+const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2
+const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3
+
+// new flags
+const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
+const float MONSTERFLAG_NORESPAWN = 4;
+const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
+const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
+const float MONSTERFLAG_MINIBOSS = 64;  // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
+
+.void() monster_spawnfunc;
+
+.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;
+
index 95341c1..1809ead 100644 (file)
@@ -345,6 +345,7 @@ void Send_Notification_WOCOVA(
        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"), "") \
        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"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s")) \
        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"), "") \
+       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"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") \
        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"), "") \
        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"), "") \
@@ -375,6 +376,29 @@ void Send_Notification_WOCOVA(
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_GENERIC,             2, 1, "s1 s2loc spree_lost", "s1",       "notify_selfkill",      _("^BG%s^K1 died%s%s"), "") \
        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"), _("^BG%s^K1 found a hot place%s%s")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
+       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"), "") \
        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?"), _("^BG%s^K1 ran out of ammo%s%s")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_ROT,                 2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 rotted away%s%s"), "") \
        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"), "") \
@@ -433,6 +457,7 @@ void Send_Notification_WOCOVA(
        MULTITEAM_INFO(1, INFO_KEYHUNT_PICKUP_, 4,             1, 0, "s1", "",                          "",                     _("^BG%s^BG picked up the ^TC^TT Key"), "") \
        MSG_INFO_NOTIF(1, INFO_LMS_FORFEIT,                    1, 0, "s1", "",                          "",                     _("^BG%s^F3 forfeited"), "") \
        MSG_INFO_NOTIF(1, INFO_LMS_NOLIVES,                    1, 0, "s1", "",                          "",                     _("^BG%s^F3 has no more lives left"), "") \
+       MSG_INFO_NOTIF(1, INFO_MONSTERS_DISABLED,                  0, 0, "", "",                            "",                     _("^BGMonsters are currently disabled"), "") \
        MSG_INFO_NOTIF(1, INFO_POWERUP_INVISIBILITY,           1, 0, "s1", "s1",                        "strength",             _("^BG%s^K1 picked up Invisibility"), "") \
        MSG_INFO_NOTIF(1, INFO_POWERUP_SHIELD,                 1, 0, "s1", "s1",                        "shield",               _("^BG%s^K1 picked up Shield"), "") \
        MSG_INFO_NOTIF(1, INFO_POWERUP_SPEED,                  1, 0, "s1", "s1",                        "shield",               _("^BG%s^K1 picked up Speed"), "") \
@@ -560,6 +585,7 @@ void Send_Notification_WOCOVA(
        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_NADE,                             0, 0, "",                         NO_CPID,                         "0 0", _("^K1You forgot to put the pin back in!"), _("^K1Tastes like chicken!")) \
        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")) \
@@ -659,6 +685,7 @@ void Send_Notification_WOCOVA(
        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_NADE,                    NO_MSG,        INFO_DEATH_MURDER_NADE,                    NO_MSG) \
        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) \
@@ -688,6 +715,29 @@ void Send_Notification_WOCOVA(
        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_NADE,                                              NO_MSG,                INFO_DEATH_SELF_NADE,                                      CENTER_DEATH_SELF_NADE) \
        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) \
index f009716..1de86b6 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"
index 3036278..97e17ff 100644 (file)
@@ -25,6 +25,7 @@ oo/base.h
 ../common/command/generic.qh
 ../common/command/shared_defs.qh
 ../common/urllib.qh
+../common/monsters/monsters.qh
 
 command/menu_cmd.qh
 menu.qh
@@ -52,6 +53,7 @@ xonotic/util.qc
 ../common/mapinfo.qc
 ../common/items.qc
 ../common/urllib.qc
+../common/monsters/monsters.qc
 
 ../warpzonelib/mathlib.qc
 
diff --git a/qcsrc/menu/xonotic/dialog_monstertools.c b/qcsrc/menu/xonotic/dialog_monstertools.c
new file mode 100644 (file)
index 0000000..a6486e2
--- /dev/null
@@ -0,0 +1,58 @@
+#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;
+
+       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.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", 0));
+               me.TD(me, 1, 0.5, e = makeXonoticCommandButton(_("Remove"), '0 0 0', "cmd mobkill", 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 :-) */
index f9d86c2..120d802 100644 (file)
@@ -199,6 +199,10 @@ void MainWindow_configureMainWindow(entity me)
        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 dfc923b..c7d851b 100644 (file)
@@ -109,7 +109,11 @@ void accuracy_add(entity e, float w, float fired, float hit)
 
 float accuracy_isgooddamage(entity attacker, entity targ)
 {
+       float targ_isvalid = ((g_invasion) ? targ.flags & FL_MONSTER : IS_CLIENT(targ));
+
        if(!warmup_stage)
+       if(targ_isvalid)
+       if not(attacker.flags & FL_MONSTER) // no accuracy for monsters
        if(IS_CLIENT(targ))
        if(targ.deadflag == DEAD_NO)
        if(DIFF_TEAM(attacker, targ))
index 711d914..72558a4 100644 (file)
@@ -730,6 +730,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;
@@ -1228,10 +1229,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_attack_range;
+float autocvar_g_monsters_typefrag;
+float autocvar_g_monsters_owners;
+float autocvar_g_monsters_miniboss_chance;
+float autocvar_g_monsters_miniboss_healthboost;
+float autocvar_g_monsters_drop_time;
+float autocvar_g_monsters_spawnshieldtime;
+float autocvar_g_monsters_teams;
+float autocvar_g_monsters_respawn_delay;
+float autocvar_g_monsters_respawn;
+float autocvar_g_monsters_armor_blockpercent;
 float autocvar_g_touchexplode_radius;
 float autocvar_g_touchexplode_damage;
 float autocvar_g_touchexplode_edgedamage;
 float autocvar_g_touchexplode_force;
+float autocvar_g_invasion_round_timelimit;
+#define autocvar_g_invasion_round_limit cvar("g_invasion_round_limit")
+float autocvar_g_invasion_warmup;
+float autocvar_g_invasion_monster_count;
+float autocvar_g_invasion_zombies_only;
 #define autocvar_g_bloodloss cvar("g_bloodloss")
 float autocvar_g_random_gravity_negative_chance;
 float autocvar_g_random_gravity_min;
index 578306c..3dd0b84 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 baf696f..0c27100 100644 (file)
@@ -168,6 +168,8 @@ void PutObserverInServer (void)
 
        Portal_ClearAll(self);
        
+       Unfreeze(self);
+       
        if(self.alivetime)
        {
                if(!warmup_stage)
@@ -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);
 
@@ -1719,6 +1726,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);
@@ -2229,6 +2238,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
@@ -2348,7 +2367,7 @@ void PlayerPreThink (void)
                        do_crouch = 0;
                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;
@@ -2408,6 +2427,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 b5b6ad7..ea193f1 100644 (file)
@@ -19,6 +19,9 @@ When you press the jump key
 */
 void PlayerJump (void)
 {
+       if(self.frozen)
+               return; // no jumping in freezetag when frozen
+               
        float doublejump = FALSE;
 
        player_multijump = doublejump;
@@ -784,6 +787,19 @@ void SV_PlayerPhysics()
        self.disableclientprediction = 0;
        if(time < self.ladder_time)
                self.disableclientprediction = 1;
+               
+       if(self.frozen)
+       {
+               if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
+               {
+                       self.movement_x = bound(-5, self.movement_x, 5);
+                       self.movement_y = bound(-5, self.movement_y, 5);
+                       self.movement_z = bound(-5, self.movement_z, 5);
+               }
+               else
+                       self.movement = '0 0 0';
+               self.disableclientprediction = 1;
+       }
 
        MUTATOR_CALLHOOK(PlayerPhysics);
 
@@ -986,7 +1002,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 e5e0c1d..07da206 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 2fc2330..5e704a4 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 f530b54..f651a3b 100644 (file)
@@ -163,7 +163,7 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector m
        // track max damage
        if(accuracy_canbegooddamage(ent))
                accuracy_add(ent, ent.weapon, maxdamage, 0);
-
+               
        W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
 
        if(ent.weaponentity.movedir_x > 0)
index 4850049..5baf7de 100644 (file)
@@ -182,6 +182,135 @@ 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 "skin": if(trace_ent.monsterid != MON_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 skin 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.monster_name, "' 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;
+                       float moveflag;
+                       
+                       moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
+                       tospawn = strtolower(argv(1));
+                       
+                       if(tospawn == "list")
+                       {
+                               float i;
+                               string list = "Available monsters:";
+                               
+                               for(i = MON_FIRST; i <= MON_LAST; ++i)
+                                       list = strcat(list, " ", (get_monsterinfo(i)).netname);
+                               
+                               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(g_invasion) { sprint(self, "You can't spawn monsters during an invasion!\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(self.deadflag != DEAD_NO) { 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);
+                               
+                               sprint(self, strcat("Spawned ", e.monster_name, "\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 +693,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 e64cbc2..120bf3e 100644 (file)
@@ -8,7 +8,10 @@
 .float lms_spectate_warning;
 .float checkfail;
 
+// number of monsters spawned with mobspawn command
+float totalspawned;
+
 string MapVote_Suggest(string m);
 
 // 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 b336d3e..569480b 100644 (file)
@@ -139,6 +139,56 @@ void GameCommand_adminmsg(float request, float argc)
        }
 }
 
+void GameCommand_butcher(float request)
+{
+       switch(request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; }
+                       if(g_invasion) { print("This command doesn't work during an invasion.\n"); return; }
+               
+            float removed_count = 0;
+                       entity head;
+                       
+                       FOR_EACH_MONSTER(head)
+                       {
+                               if(head.weaponentity)
+                                       remove(head.weaponentity);
+                                       
+                               if(head.iceblock)
+                                       remove(head.iceblock);
+                                       
+                               remove(head);
+                               ++removed_count;
+                       }
+                       
+                       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)
@@ -1728,6 +1778,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 c7aebd5..6d720f1 100644 (file)
@@ -581,7 +581,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 6a6587e..4cae477 100644 (file)
@@ -549,6 +549,77 @@ 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)
+{
+       if(!IS_PLAYER(targ) && !(targ.flags & FL_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, head;
+       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);
+       
+       FOR_EACH_PLAYER(head)
+       if(head.hook.aiment == targ)
+               RemoveGrapplingHook(head);
+       
+       // 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(targ.frozen) // only reset health if target was frozen
+               targ.health = ((IS_PLAYER(targ)) ? autocvar_g_balance_health_start : targ.max_health);
+
+       entity head;
+       targ.frozen = 0;
+       targ.revive_progress = 0;
+       
+       WaypointSprite_Kill(targ.waypointsprite_attached);
+       
+       FOR_EACH_PLAYER(head)
+       if(head.hook.aiment == targ)
+               RemoveGrapplingHook(head);
+
+       // remove the ice block
+       if(targ.iceblock)
+               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;
@@ -678,12 +749,18 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        mirrorforce *= g_weaponforcefactor;
                }
                
+               if(((targ.frozen == 2 && attacker.monsterid != MON_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;
                frag_damage = damage;
                frag_force = force;
-        frag_deathtype = deathtype;
+               frag_deathtype = deathtype;
                frag_mirrordamage = mirrordamage;
                MUTATOR_CALLHOOK(PlayerDamage_Calculate);
                damage = frag_damage;
@@ -732,7 +809,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(DIFF_TEAM(victim, attacker))
                                {
@@ -1200,7 +1277,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 d654b91..750f1c4 100644 (file)
@@ -545,6 +545,7 @@ void spawnfunc___init_dedicated_server(void)
 
        // needs to be done so early because of the constants they create
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+       CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
@@ -593,6 +594,7 @@ void spawnfunc_worldspawn (void)
 
        // needs to be done so early because of the constants they create
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+       CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
@@ -794,6 +796,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);
@@ -804,6 +810,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);
index 0b5b427..dec1502 100644 (file)
@@ -99,6 +99,8 @@ const 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)
@@ -946,6 +948,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");
index 9b9f7fd..b8f46b5 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 98a4494..bd70e7c 100644 (file)
@@ -152,6 +152,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 f95d956..a5de5b2 100644 (file)
@@ -431,14 +431,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_));
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
+       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.flags & FL_MONSTER) ? player.monster_name : player.netname);
        sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_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
        
@@ -780,13 +785,19 @@ void ctf_FlagTouch()
        }
        
        // special touch behaviors
-       if(toucher.vehicle_flags & VHF_ISVEHICLE)
+       if(toucher.frozen) { return; }
+       else if(toucher.vehicle_flags & VHF_ISVEHICLE)
        {
                if(autocvar_g_ctf_allow_vehicle_touch)
                        toucher = toucher.owner; // the player is actually the vehicle owner, not other
                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
@@ -803,9 +814,9 @@ void ctf_FlagTouch()
        {       
                case FLAG_BASE:
                {
-                       if(SAME_TEAM(toucher, self) &&