]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into martin-t/effects
authorMartin Taibr <taibr.martin@gmail.com>
Sat, 20 Oct 2018 14:29:33 +0000 (16:29 +0200)
committerMartin Taibr <taibr.martin@gmail.com>
Sat, 20 Oct 2018 14:29:33 +0000 (16:29 +0200)
522 files changed:
.gitlab-ci.yml
.tx/merge-base
_hud_common.cfg
bal-wep-mario.cfg
bal-wep-xdf.cfg
binds-xonotic.cfg
commands.cfg
common.de.po
common.de_CH.po
common.he.po
common.hu.po
common.pt_BR.po
common.ru.po
common.zh_CN.po
effectinfo.txt
gamemodes-client.cfg
gamemodes-server.cfg
gfx/hud/luma/weaponhmg.tga
gfx/hud/luma/weaponrpc.tga
gfx/menu/luma/bigbutton_c.tga
gfx/menu/luma/bigbutton_d.tga
gfx/menu/luma/bigbutton_f.tga
gfx/menu/luma/bigbutton_n.tga
gfx/menu/luma/bigbuttongray_c.tga
gfx/menu/luma/bigbuttongray_d.tga
gfx/menu/luma/bigbuttongray_f.tga
gfx/menu/luma/bigbuttongray_n.tga
gfx/menu/luma/border.tga
gfx/menu/luma/button_c.tga
gfx/menu/luma/button_d.tga
gfx/menu/luma/button_f.tga
gfx/menu/luma/button_n.tga
gfx/menu/luma/buttongray_c.tga
gfx/menu/luma/buttongray_d.tga
gfx/menu/luma/buttongray_f.tga
gfx/menu/luma/buttongray_n.tga
gfx/menu/luma/checkbox_c0.tga
gfx/menu/luma/checkbox_c1.tga
gfx/menu/luma/checkbox_d0.tga
gfx/menu/luma/checkbox_d1.tga
gfx/menu/luma/checkbox_f0.tga
gfx/menu/luma/checkbox_f1.tga
gfx/menu/luma/checkbox_n0.tga
gfx/menu/luma/checkbox_n1.tga
gfx/menu/luma/checkmark.tga
gfx/menu/luma/clearbutton_c.tga
gfx/menu/luma/clearbutton_f.tga
gfx/menu/luma/clearbutton_n.tga
gfx/menu/luma/closebutton_c.tga
gfx/menu/luma/closebutton_f.tga
gfx/menu/luma/closebutton_n.tga
gfx/menu/luma/colorbutton_c.tga
gfx/menu/luma/colorbutton_f.tga
gfx/menu/luma/colorbutton_n.tga
gfx/menu/luma/colorpicker_m.tga
gfx/menu/luma/cursor.tga
gfx/menu/luma/cursor_move.tga
gfx/menu/luma/cursor_resize.tga
gfx/menu/luma/cursor_resize2.tga
gfx/menu/luma/gametype_as.tga
gfx/menu/luma/gametype_ca.tga
gfx/menu/luma/gametype_ctf.tga
gfx/menu/luma/gametype_cts.tga
gfx/menu/luma/gametype_dm.tga
gfx/menu/luma/gametype_dom.tga
gfx/menu/luma/gametype_duel.tga
gfx/menu/luma/gametype_ft.tga
gfx/menu/luma/gametype_inf.tga
gfx/menu/luma/gametype_inv.tga
gfx/menu/luma/gametype_jb.tga
gfx/menu/luma/gametype_ka.tga
gfx/menu/luma/gametype_kh.tga
gfx/menu/luma/gametype_lms.tga
gfx/menu/luma/gametype_nb.tga
gfx/menu/luma/gametype_ons.tga
gfx/menu/luma/gametype_rc.tga
gfx/menu/luma/gametype_tdm.tga
gfx/menu/luma/gametype_vip.tga
gfx/menu/luma/icon_aeslevel1.tga
gfx/menu/luma/icon_aeslevel2.tga
gfx/menu/luma/icon_aeslevel3.tga
gfx/menu/luma/icon_aeslevel4.tga
gfx/menu/luma/icon_aeslevel5.tga
gfx/menu/luma/icon_ipv4.tga
gfx/menu/luma/icon_ipv6.tga
gfx/menu/luma/icon_mod_.tga
gfx/menu/luma/icon_mod_XDF.tga [deleted file]
gfx/menu/luma/icon_mod_instagib.tga
gfx/menu/luma/icon_mod_jeff.tga
gfx/menu/luma/icon_mod_minstagib.tga
gfx/menu/luma/icon_mod_newtoys.tga
gfx/menu/luma/icon_mod_overkill.tga
gfx/menu/luma/icon_mod_quake.tga
gfx/menu/luma/icon_mod_xdf.tga [new file with mode: 0644]
gfx/menu/luma/icon_mod_xpm.tga [new file with mode: 0644]
gfx/menu/luma/icon_pure1.tga
gfx/menu/luma/icon_stats1.tga
gfx/menu/luma/inputbox_f.tga
gfx/menu/luma/inputbox_n.tga
gfx/menu/luma/nopreview_map.tga
gfx/menu/luma/nopreview_menuskin.tga
gfx/menu/luma/nopreview_player.tga
gfx/menu/luma/radiobutton_c0.tga
gfx/menu/luma/radiobutton_c1.tga
gfx/menu/luma/radiobutton_d0.tga
gfx/menu/luma/radiobutton_d1.tga
gfx/menu/luma/radiobutton_f0.tga
gfx/menu/luma/radiobutton_f1.tga
gfx/menu/luma/radiobutton_n0.tga
gfx/menu/luma/radiobutton_n1.tga
gfx/menu/luma/scrollbar_c.tga
gfx/menu/luma/scrollbar_f.tga
gfx/menu/luma/scrollbar_n.tga
gfx/menu/luma/scrollbar_s.tga
gfx/menu/luma/skinpreview.tga
gfx/menu/luma/skinvalues.txt
gfx/menu/luma/slider_c.tga
gfx/menu/luma/slider_d.tga
gfx/menu/luma/slider_f.tga
gfx/menu/luma/slider_n.tga
gfx/menu/luma/slider_s.tga
gfx/menu/luma/tooltip.tga
gfx/menu/luminos/icon_mod_xpm.tga [new file with mode: 0644]
gfx/menu/wickedx/icon_mod_xpm.tga [new file with mode: 0644]
gfx/menu/xaw/icon_mod_xpm.tga [new file with mode: 0644]
gfx/scoreboard/player_ready.tga
gfx/scoreboard/playercolor_base.tga
gfx/scoreboard/playercolor_pants.tga
gfx/scoreboard/playercolor_shirt.tga
gfx/scoreboard/scoreboard_bg.tga
gfx/scoreboard/scoreboard_tableheader.tga
help-overkill.cfg [new file with mode: 0644]
help-xonotic.cfg [new file with mode: 0644]
help.cfg [new file with mode: 0644]
languages.txt
models/items/a_cells.md3
models/weapons/g_crylink.md3
models/weapons/g_electro.md3
models/weapons/h_crylink.iqm
models/weapons/h_crylink.iqm.framegroups
models/weapons/h_electro.iqm
models/weapons/h_electro.iqm.framegroups
models/weapons/v_crylink.md3
models/weapons/v_electro.md3
notifications.cfg
qcsrc/client/autocvars.qh
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/defs.qh
qcsrc/client/hud/hud.qc
qcsrc/client/hud/hud.qh
qcsrc/client/hud/hud_config.qc
qcsrc/client/hud/panel/infomessages.qc
qcsrc/client/hud/panel/modicons.qc
qcsrc/client/hud/panel/physics.qc
qcsrc/client/hud/panel/powerups.qc
qcsrc/client/hud/panel/quickmenu.qc
qcsrc/client/hud/panel/racetimer.qc
qcsrc/client/hud/panel/radar.qc
qcsrc/client/hud/panel/score.qc
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/hud/panel/scoreboard.qh
qcsrc/client/hud/panel/timer.qc
qcsrc/client/hud/panel/vote.qc
qcsrc/client/main.qc
qcsrc/client/main.qh
qcsrc/client/mapvoting.qc
qcsrc/client/mapvoting.qh
qcsrc/client/shownames.qc
qcsrc/client/view.qc
qcsrc/client/view.qh
qcsrc/client/weapons/projectile.qc
qcsrc/common/animdecide.qc
qcsrc/common/campaign_common.qh
qcsrc/common/campaign_file.qc
qcsrc/common/command/generic.qc
qcsrc/common/command/generic.qh
qcsrc/common/command/rpn.qc
qcsrc/common/command/rpn.qh
qcsrc/common/debug.qh
qcsrc/common/effects/all.inc
qcsrc/common/effects/effectinfo.inc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/assault/_mod.inc
qcsrc/common/gamemodes/gamemode/assault/_mod.qh
qcsrc/common/gamemodes/gamemode/assault/assault.qc [deleted file]
qcsrc/common/gamemodes/gamemode/assault/assault.qh [deleted file]
qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/assault/sv_assault.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc
qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh
qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc [deleted file]
qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh [deleted file]
qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ctf/_mod.inc
qcsrc/common/gamemodes/gamemode/ctf/_mod.qh
qcsrc/common/gamemodes/gamemode/ctf/ctf.qc [deleted file]
qcsrc/common/gamemodes/gamemode/ctf/ctf.qh
qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/cts/_mod.inc
qcsrc/common/gamemodes/gamemode/cts/_mod.qh
qcsrc/common/gamemodes/gamemode/cts/cts.qc [deleted file]
qcsrc/common/gamemodes/gamemode/cts/cts.qh [deleted file]
qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/cts/sv_cts.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc
qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh
qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc [deleted file]
qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh [deleted file]
qcsrc/common/gamemodes/gamemode/deathmatch/sv_deathmatch.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/deathmatch/sv_deathmatch.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/domination/_mod.inc
qcsrc/common/gamemodes/gamemode/domination/_mod.qh
qcsrc/common/gamemodes/gamemode/domination/domination.qc [deleted file]
qcsrc/common/gamemodes/gamemode/domination/domination.qh [deleted file]
qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/domination/sv_domination.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/duel/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/duel/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/duel/sv_duel.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/duel/sv_duel.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc
qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh
qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc [deleted file]
qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh [deleted file]
qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/invasion/_mod.inc
qcsrc/common/gamemodes/gamemode/invasion/_mod.qh
qcsrc/common/gamemodes/gamemode/invasion/invasion.qc [deleted file]
qcsrc/common/gamemodes/gamemode/invasion/invasion.qh [deleted file]
qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc
qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh
qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc [deleted file]
qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh [deleted file]
qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc
qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh
qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc [deleted file]
qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh [deleted file]
qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/lms/_mod.inc
qcsrc/common/gamemodes/gamemode/lms/_mod.qh
qcsrc/common/gamemodes/gamemode/lms/lms.qc [deleted file]
qcsrc/common/gamemodes/gamemode/lms/lms.qh [deleted file]
qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/lms/sv_lms.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
qcsrc/common/gamemodes/gamemode/nexball/sv_weapon.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qh
qcsrc/common/gamemodes/gamemode/race/_mod.inc
qcsrc/common/gamemodes/gamemode/race/_mod.qh
qcsrc/common/gamemodes/gamemode/race/race.qc [deleted file]
qcsrc/common/gamemodes/gamemode/race/race.qh [deleted file]
qcsrc/common/gamemodes/gamemode/race/sv_race.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/race/sv_race.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/_mod.inc
qcsrc/common/gamemodes/gamemode/tdm/_mod.qh
qcsrc/common/gamemodes/gamemode/tdm/sv_tdm.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/sv_tdm.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/tdm.qc [deleted file]
qcsrc/common/gamemodes/gamemode/tdm/tdm.qh [deleted file]
qcsrc/common/gamemodes/sv_rules.qh
qcsrc/common/impulses/all.qh
qcsrc/common/items/inventory.qh
qcsrc/common/items/item.qh
qcsrc/common/items/item/ammo.qh
qcsrc/common/items/item/armor.qh
qcsrc/common/items/item/health.qh
qcsrc/common/items/item/jetpack.qh
qcsrc/common/items/item/pickup.qc
qcsrc/common/items/item/powerup.qh
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/mapobjects/func/bobbing.qc
qcsrc/common/mapobjects/func/button.qc
qcsrc/common/mapobjects/func/conveyor.qc
qcsrc/common/mapobjects/func/door.qc
qcsrc/common/mapobjects/func/plat.qc
qcsrc/common/mapobjects/misc/_mod.inc
qcsrc/common/mapobjects/misc/_mod.qh
qcsrc/common/mapobjects/misc/keys.qc [new file with mode: 0644]
qcsrc/common/mapobjects/misc/keys.qh [new file with mode: 0644]
qcsrc/common/mapobjects/models.qc
qcsrc/common/mapobjects/models.qh
qcsrc/common/mapobjects/subs.qh
qcsrc/common/mapobjects/trigger/counter.qc
qcsrc/common/mapobjects/trigger/counter.qh
qcsrc/common/mapobjects/trigger/hurt.qc
qcsrc/common/mapobjects/trigger/hurt.qh
qcsrc/common/mapobjects/trigger/jumppads.qc
qcsrc/common/mapobjects/trigger/jumppads.qh
qcsrc/common/mapobjects/trigger/keylock.qc
qcsrc/common/mapobjects/trigger/keylock.qh
qcsrc/common/mapobjects/triggers.qc
qcsrc/common/mapobjects/triggers.qh
qcsrc/common/minigames/cl_minigames_hud.qc
qcsrc/common/minigames/minigame/bd.qc
qcsrc/common/minigames/minigame/ps.qc
qcsrc/common/minigames/sv_minigames.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/mutators/mutator/buffs/all.inc
qcsrc/common/mutators/mutator/buffs/buffs.qc
qcsrc/common/mutators/mutator/buffs/buffs.qh
qcsrc/common/mutators/mutator/buffs/cl_buffs.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc
qcsrc/common/mutators/mutator/dynamic_handicap/sv_dynamic_handicap.qc
qcsrc/common/mutators/mutator/instagib/items.qh
qcsrc/common/mutators/mutator/nades/nades.qc
qcsrc/common/mutators/mutator/overkill/okhmg.qc
qcsrc/common/mutators/mutator/overkill/okmachinegun.qc
qcsrc/common/mutators/mutator/overkill/okmachinegun.qh
qcsrc/common/mutators/mutator/overkill/oknex.qc
qcsrc/common/mutators/mutator/overkill/oknex.qh
qcsrc/common/mutators/mutator/overkill/okrpc.qc
qcsrc/common/mutators/mutator/overkill/okshotgun.qc
qcsrc/common/mutators/mutator/overkill/okshotgun.qh
qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc
qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc
qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc
qcsrc/common/mutators/mutator/waypoints/all.inc
qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc
qcsrc/common/mutators/mutator/waypoints/waypointsprites.qh
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/physics/movetypes/movetypes.qc
qcsrc/common/physics/movetypes/movetypes.qh
qcsrc/common/physics/movetypes/walk.qc
qcsrc/common/physics/player.qc
qcsrc/common/physics/player.qh
qcsrc/common/playerstats.qc
qcsrc/common/playerstats.qh
qcsrc/common/sounds/all.qc
qcsrc/common/state.qc
qcsrc/common/stats.qh
qcsrc/common/t_items.qc
qcsrc/common/t_items.qh
qcsrc/common/teams.qh
qcsrc/common/turrets/cl_turrets.qc
qcsrc/common/turrets/sv_turrets.qc
qcsrc/common/turrets/turret.qh
qcsrc/common/turrets/turret/hk.qc
qcsrc/common/turrets/turret/machinegun_weapon.qc
qcsrc/common/turrets/turret/walker_weapon.qc
qcsrc/common/util.qc
qcsrc/common/vehicles/sv_vehicles.qc
qcsrc/common/vehicles/sv_vehicles.qh
qcsrc/common/vehicles/vehicle/bumblebee.qc
qcsrc/common/vehicles/vehicle/bumblebee_weapons.qh
qcsrc/common/vehicles/vehicle/racer.qc
qcsrc/common/vehicles/vehicle/raptor.qc
qcsrc/common/vehicles/vehicle/spiderbot.qc
qcsrc/common/viewloc.qc
qcsrc/common/weapons/all.qh
qcsrc/common/weapons/config.qc
qcsrc/common/weapons/weapon.qh
qcsrc/common/weapons/weapon/arc.qc
qcsrc/common/weapons/weapon/crylink.qc
qcsrc/common/weapons/weapon/devastator.qc
qcsrc/common/weapons/weapon/electro.qc
qcsrc/common/weapons/weapon/fireball.qc
qcsrc/common/weapons/weapon/hagar.qc
qcsrc/common/weapons/weapon/hlac.qc
qcsrc/common/weapons/weapon/machinegun.qc
qcsrc/common/weapons/weapon/minelayer.qc
qcsrc/common/weapons/weapon/mortar.qc
qcsrc/common/weapons/weapon/porto.qc
qcsrc/common/weapons/weapon/rifle.qc
qcsrc/common/weapons/weapon/seeker.qc
qcsrc/common/weapons/weapon/shockwave.qc
qcsrc/common/weapons/weapon/shotgun.qc
qcsrc/common/weapons/weapon/vaporizer.qc
qcsrc/common/weapons/weapon/vortex.qc
qcsrc/common/wepent.qc
qcsrc/common/wepent.qh
qcsrc/lib/csqcmodel/cl_player.qc
qcsrc/lib/warpzone/client.qc
qcsrc/menu/command/menu_cmd.qc
qcsrc/menu/item.qc
qcsrc/menu/item.qh
qcsrc/menu/item/container.qh
qcsrc/menu/item/image.qh
qcsrc/menu/item/inputbox.qc
qcsrc/menu/item/label.qh
qcsrc/menu/item/listbox.qc
qcsrc/menu/item/listbox.qh
qcsrc/menu/xonotic/campaign.qc
qcsrc/menu/xonotic/campaign.qh
qcsrc/menu/xonotic/crosshairpreview.qh
qcsrc/menu/xonotic/demolist.qc
qcsrc/menu/xonotic/dialog_multiplayer_create.qc
qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.qc
qcsrc/menu/xonotic/dialog_settings_game_messages.qc
qcsrc/menu/xonotic/dialog_settings_misc.qc
qcsrc/menu/xonotic/gametypelist.qc
qcsrc/menu/xonotic/hudskinlist.qc
qcsrc/menu/xonotic/keybinder.qc
qcsrc/menu/xonotic/maplist.qc
qcsrc/menu/xonotic/picker.qh
qcsrc/menu/xonotic/playerlist.qc
qcsrc/menu/xonotic/playlist.qc
qcsrc/menu/xonotic/screenshotlist.qc
qcsrc/menu/xonotic/skinlist.qc
qcsrc/menu/xonotic/soundlist.qc
qcsrc/menu/xonotic/statslist.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/autocvars.qh
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/aim.qc
qcsrc/server/bot/default/aim.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/bot.qh
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/havocbot/havocbot.qh
qcsrc/server/bot/default/havocbot/roles.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/bot/default/navigation.qh
qcsrc/server/bot/default/scripting.qc
qcsrc/server/bot/default/waypoints.qc
qcsrc/server/bot/default/waypoints.qh
qcsrc/server/cheats.qc
qcsrc/server/cheats.qh
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/clientkill.qc [new file with mode: 0644]
qcsrc/server/clientkill.qh [new file with mode: 0644]
qcsrc/server/command/banning.qc
qcsrc/server/command/banning.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/common.qh
qcsrc/server/command/radarmap.qc
qcsrc/server/command/radarmap.qh
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/vote.qc
qcsrc/server/command/vote.qh
qcsrc/server/compat/quake.qc
qcsrc/server/compat/quake3.qc
qcsrc/server/compat/quake3.qh
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_damage.qh
qcsrc/server/g_world.qc
qcsrc/server/g_world.qh
qcsrc/server/impulse.qc
qcsrc/server/ipban.qc
qcsrc/server/item_key.qc [deleted file]
qcsrc/server/item_key.qh [deleted file]
qcsrc/server/matrix.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/miscfunctions.qh
qcsrc/server/mutators/events.qh
qcsrc/server/player.qc
qcsrc/server/player.qh
qcsrc/server/portals.qc
qcsrc/server/round_handler.qc
qcsrc/server/round_handler.qh
qcsrc/server/scores_rules.qc
qcsrc/server/spawnpoints.qc
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
qcsrc/server/weapons/accuracy.qc
qcsrc/server/weapons/accuracy.qh
qcsrc/server/weapons/selection.qc
qcsrc/server/weapons/selection.qh
qcsrc/server/weapons/spawning.qc
qcsrc/server/weapons/throwing.qc
qcsrc/server/weapons/throwing.qh
qcsrc/server/weapons/tracing.qc
qcsrc/server/weapons/tracing.qh
qcsrc/server/weapons/weaponsystem.qc
ruleset-XPM.cfg
ruleset-overkill.cfg
scripts/cellammo.Shader [new file with mode: 0644]
scripts/crylink.shader [new file with mode: 0644]
scripts/electro.shader
scripts/weapons.shader
textures/cellammoskin.jpg [deleted file]
textures/cellammoskin_glow.jpg [deleted file]
textures/crylink.tga [deleted file]
textures/crylink_bump.tga [deleted file]
textures/crylink_gloss.tga [deleted file]
textures/crylink_glow.jpg [deleted file]
textures/crylink_new.tga [new file with mode: 0644]
textures/crylink_new_gloss.tga [new file with mode: 0644]
textures/crylink_new_glow.tga [new file with mode: 0644]
textures/crylink_new_norm.tga [new file with mode: 0644]
textures/crylink_new_reflect.tga [new file with mode: 0644]
textures/crylink_new_shirt.tga [new file with mode: 0644]
textures/crylink_reflect.tga [deleted file]
textures/crylink_shirt.tga [deleted file]
textures/electro.tga [deleted file]
textures/electro_gloss.tga [deleted file]
textures/electro_glow.tga [deleted file]
textures/electro_norm.tga [deleted file]
textures/electro_reflect.tga [deleted file]
textures/electronew.tga [new file with mode: 0644]
textures/electronew_gloss.tga [new file with mode: 0644]
textures/electronew_glow.tga [new file with mode: 0644]
textures/electronew_norm.tga [new file with mode: 0644]
textures/electronew_reflect.tga [new file with mode: 0644]
textures/electronew_shirt.tga [new file with mode: 0644]
textures/items/cellammo.tga [new file with mode: 0644]
textures/items/cellammo_gloss.tga [new file with mode: 0644]
textures/items/cellammo_glow.tga [new file with mode: 0644]
textures/items/cellammo_norm.tga [new file with mode: 0644]
textures/items/cellammo_reflect.tga [new file with mode: 0644]
vehicles.cfg [new file with mode: 0644]
xonotic-client.cfg
xonotic-common.cfg
xonotic-server.cfg

index 153b172f2bc2986096b04662f7ec1e011bfe617f..66b075642f6f069c3548aacf7ba8a8d646ad7a59 100644 (file)
@@ -29,7 +29,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
     - make
-    - EXPECT=b8f4fa5002af1f9f2d5ac3d1809ed188
+    - EXPECT=538cd6a692f22150ac60d9902eddc75b
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index 0d8e11ef08165bfa343f580d028cc2a8b5811370..3512ca5dc542939aff93cadaafcc1544cf16655e 100644 (file)
@@ -1 +1 @@
-Wed Jul 11 07:24:32 CEST 2018
+Sun Sep 30 07:24:04 CEST 2018
index bcf0026f6550225cfee3c4d1dc891c0d1862c318..75e8eb8d6f51a5d4ee6b94b804aea9eaac9a0d45 100644 (file)
@@ -115,6 +115,9 @@ seta hud_panel_scoreboard_spectators_showping 1 "show ping of spectators"
 seta hud_panel_scoreboard_spectators_aligned 0 "align spectators in columns"
 seta hud_panel_scoreboard_minwidth 0.6 "minimum width of the scoreboard"
 
+seta hud_panel_scoreboard_accuracy_showdelay 2 "how long to delay displaying accuracy below the scoreboard if it's too far down"
+seta hud_panel_scoreboard_accuracy_showdelay_minpos 0.75 "delay displaying the accuracy panel only if its position is lower than this percentage of the screen height from the top"
+
 // hud panel aliases
 alias quickmenu "cl_cmd hud quickmenu ${* ?}"
 
index d2ff12f6bc81cf2e13051600315fd4fb78f9c2ca..45a75349be2cfba6658cd4199f6a25aba847350c 100644 (file)
@@ -60,8 +60,8 @@ set g_balance_shotgun_secondary_alt_animtime 0.2
 set g_balance_shotgun_secondary_alt_refire 1.2
 set g_balance_shotgun_switchdelay_drop 0.2
 set g_balance_shotgun_switchdelay_raise 0.2
-set g_balance_shotgun_weaponreplace "shockwave"
-set g_balance_shotgun_weaponstart 0
+set g_balance_shotgun_weaponreplace ""
+set g_balance_shotgun_weaponstart 1
 set g_balance_shotgun_weaponstartoverride -1
 set g_balance_shotgun_weaponthrowable 1
 // }}}
@@ -260,9 +260,9 @@ set g_balance_crylink_secondary_ammo 3
 set g_balance_crylink_secondary_animtime 0.2
 set g_balance_crylink_secondary_bouncedamagefactor 0.5
 set g_balance_crylink_secondary_bounces 0
-set g_balance_crylink_secondary_damage 50
-set g_balance_crylink_secondary_edgedamage 15
-set g_balance_crylink_secondary_force -400
+set g_balance_crylink_secondary_damage 10
+set g_balance_crylink_secondary_edgedamage 5
+set g_balance_crylink_secondary_force -200
 set g_balance_crylink_secondary_joindelay 0
 set g_balance_crylink_secondary_joinexplode 0
 set g_balance_crylink_secondary_joinexplode_damage 0
@@ -270,17 +270,17 @@ set g_balance_crylink_secondary_joinexplode_edgedamage 0
 set g_balance_crylink_secondary_joinexplode_force 0
 set g_balance_crylink_secondary_joinexplode_radius 0
 set g_balance_crylink_secondary_joinspread 0
-set g_balance_crylink_secondary_linkexplode 1
+set g_balance_crylink_secondary_linkexplode 0
 set g_balance_crylink_secondary_middle_fadetime 5
 set g_balance_crylink_secondary_middle_lifetime 5
-set g_balance_crylink_secondary_other_fadetime 5
-set g_balance_crylink_secondary_other_lifetime 5
-set g_balance_crylink_secondary_radius 70
-set g_balance_crylink_secondary_refire 0.8
-set g_balance_crylink_secondary_shots 1
-set g_balance_crylink_secondary_speed 3000
-set g_balance_crylink_secondary_spread 0
-set g_balance_crylink_secondary_spreadtype 1
+set g_balance_crylink_secondary_other_fadetime 2
+set g_balance_crylink_secondary_other_lifetime 2
+set g_balance_crylink_secondary_radius 100
+set g_balance_crylink_secondary_refire 0.65
+set g_balance_crylink_secondary_shots 5
+set g_balance_crylink_secondary_speed 7000
+set g_balance_crylink_secondary_spread 0.08
+set g_balance_crylink_secondary_spreadtype 0
 set g_balance_crylink_switchdelay_drop 0.2
 set g_balance_crylink_switchdelay_raise 0.2
 set g_balance_crylink_weaponreplace ""
@@ -725,7 +725,7 @@ set g_balance_shockwave_melee_traces 10
 set g_balance_shockwave_switchdelay_drop 0.2
 set g_balance_shockwave_switchdelay_raise 0.2
 set g_balance_shockwave_weaponreplace ""
-set g_balance_shockwave_weaponstart 1
+set g_balance_shockwave_weaponstart 0
 set g_balance_shockwave_weaponstartoverride -1
 set g_balance_shockwave_weaponthrowable 0
 // }}}
@@ -753,10 +753,10 @@ set g_balance_arc_beam_heat 0
 set g_balance_arc_burst_heat 5
 set g_balance_arc_beam_maxangle 10
 set g_balance_arc_beam_nonplayerdamage 80
-set g_balance_arc_beam_range 1250
+set g_balance_arc_beam_range 1500
 set g_balance_arc_beam_refire 0.25
 set g_balance_arc_beam_returnspeed 8
-set g_balance_arc_beam_tightness 0.5
+set g_balance_arc_beam_tightness 0.6
 set g_balance_arc_bolt 1
 set g_balance_arc_bolt_ammo 1
 set g_balance_arc_bolt_damage 25
@@ -883,7 +883,7 @@ set g_balance_okmachinegun_primary_ammo 1
 set g_balance_okmachinegun_primary_damage 25
 set g_balance_okmachinegun_primary_force 5
 set g_balance_okmachinegun_primary_refire 0.1
-set g_balance_okmachinegun_primary_solidpenetration 13.1
+set g_balance_okmachinegun_primary_solidpenetration 63
 set g_balance_okmachinegun_primary_spread_add 0.012
 set g_balance_okmachinegun_primary_spread_max 0.05
 set g_balance_okmachinegun_primary_spread_min 0
index 2e0e74aa3d7a993b7a97f1ac1b43f9ead3bbc797..13bdc529a9f7364e36ce66ec85b596732e2b0f7b 100644 (file)
@@ -762,11 +762,11 @@ set g_balance_arc_bolt_ammo 1
 set g_balance_arc_bolt_damage 25
 set g_balance_arc_bolt_damageforcescale 0
 set g_balance_arc_bolt_edgedamage 12.5
-set g_balance_arc_bolt_force 120
+set g_balance_arc_bolt_force 200
 set g_balance_arc_bolt_health 15
 set g_balance_arc_bolt_lifetime 5
 set g_balance_arc_bolt_radius 65
-set g_balance_arc_bolt_refire 0.16667
+set g_balance_arc_bolt_refire 0.033333
 set g_balance_arc_bolt_speed 2300
 set g_balance_arc_bolt_spread 0
 set g_balance_arc_burst_ammo 15
index 18ee7ea28f1a100f21e29c8e0aed1d0c9fece23e..611f5da95a867d7be500d8caf38b8a84038f12e0 100644 (file)
@@ -40,6 +40,7 @@ bind MWHEELUP weapnext
 bind MWHEELDOWN weapprev
 bind r reload
 bind BACKSPACE dropweapon
+bind k kill
 bind g dropweapon
 bind f +use
 bind v +button8 // drag object
index b2edf84788332b7da31d681a318c19d6fd2e807d..0f2e5689253e3ef06acfc3c4b0cef282e7da7419 100644 (file)
@@ -40,6 +40,7 @@ if_client    "alias" help "cl_cmd help; cmd help"
 // networked/server common commands
 alias cvar_changes         "qc_cmd_svcmd  cvar_changes         ${* ?}" // Prints a list of all changed server cvars
 alias cvar_purechanges     "qc_cmd_svcmd  cvar_purechanges     ${* ?}" // Prints a list of all changed gameplay cvars
+alias editmob              "qc_cmd_svcmd  editmob              ${* ?}" // Modifies a monster or all monsters
 alias info                 "qc_cmd_svcmd  info                 ${* ?}" // Request for unique server information set up by admin
 alias ladder               "qc_cmd_svcmd  ladder               ${* ?}" // Get information about top players if supported
 alias lsmaps               "qc_cmd_svcmd  lsmaps               ${* ?}" // List maps which can be used with the current game mode
@@ -55,16 +56,26 @@ 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 bufstr_get           "qc_cmd_svmenu bufstr_get           ${* ?}" // Examine a string buffer object
+alias cvar_localchanges    "qc_cmd_svmenu cvar_localchanges    ${* ?}" // Print locally changed cvars
 alias dumpcommands         "qc_cmd_svmenu dumpcommands         ${* ?}" // Dump all commands on the program to *_cmd_dump.txt
-alias dumpnotifs           "qc_cmd_svcl   dumpnotifs           ${* ?}" // Dump all notifications into notifications_dump.txt
+alias dumpnotifs           "qc_cmd_svmenu dumpnotifs           ${* ?}" // Dump all notifications into notifications_dump.txt
+alias dumpitems            "qc_cmd_svmenu dumpitems            ${* ?}" // Dump all items to the console
+alias dumpturrets          "qc_cmd_svmenu dumpturrets          ${* ?}" // Dump all turrets into turrets_dump.txt
+alias dumpweapons          "qc_cmd_svmenu dumpweapons          ${* ?}" // Dump all weapons into weapons_dump.txt
+alias find                 "qc_cmd_svmenu find                 ${* ?}" // Search through entities for matching classname
+alias findat               "qc_cmd_svmenu findat               ${* ?}" // Search through entities for matching origin
 alias maplist              "qc_cmd_svmenu maplist              ${* ?}" // Automatic control of maplist
+alias mx                   "qc_cmd_svmenu mx                   ${* ?}" // Send a matrix command
 alias nextframe            "qc_cmd_svmenu nextframe            ${* ?}" // Execute the given command next frame of this VM
 alias qc_curl              "qc_cmd_svmenu qc_curl              ${* ?}" // Queries a URL
 alias removefromlist       "qc_cmd_svmenu removefromlist       ${* ?}" // Remove a string from a cvar
-alias restartnotifs        "qc_cmd_svcl   restartnotifs        ${* ?}" // Re-initialize all notifications
+alias restartnotifs        "qc_cmd_svmenu restartnotifs        ${* ?}" // Re-initialize all notifications
 alias rpn                  "qc_cmd_svmenu rpn                  ${* ?}" // RPN calculator
+alias runtest              "qc_cmd_svmenu runtest              ${* ?}" // Run unit tests
 //alias settemp            "qc_cmd_svmenu settemp              ${* ?}" // Temporarily set a value to a cvar which is restored later
 //alias settemp_restore    "qc_cmd_svmenu settemp_restore      ${* ?}" // Restore all cvars set by settemp command
+alias version              "qc_cmd_svmenu version              ${* ?}" // Print the current version
 
 // other aliases for common commands
 alias g_hitplots_add "qc_cmd_svmenu rpn /g_hitplots_individuals g_hitplots_individuals ${1 !} union def"
@@ -116,6 +127,7 @@ alias menu_loadmap_prepare "disconnect; wait; g_campaign 0; menu_cmd rpn /_menu_
 // ==========================================================
 // commented out commands are really only intended for internal use
 alias blurtest             "qc_cmd_cl     blurtest             ${* ?}" // Feature for testing blur postprocessing
+alias boxparticles         "qc_cmd_cl     boxparticles         ${* ?}" // Spawn particles manually
 alias create_scrshot_ent   "qc_cmd_cl     create_scrshot_ent   ${* ?}" // Create an entity at this location for automatic screenshots
 alias debugmodel           "qc_cmd_cl     debugmodel           ${* ?}" // Spawn a debug model manually
 //alias handlevote         "qc_cmd_cl     handlevote           ${* ?}" // System to handle selecting a vote or option
@@ -123,6 +135,8 @@ alias hud                  "qc_cmd_cl     hud                  ${* ?}" // Comman
 alias localprint           "qc_cmd_cl     localprint           ${* ?}" // Create your own centerprint sent to yourself
 //alias mv_download        "qc_cmd_cl     mv_download          ${* ?}" // Retrieve mapshot picture from the server
 alias sendcvar             "qc_cmd_cl     sendcvar             ${* ?}" // Send a cvar to the server (like weaponpriority)
+alias weapon_find          "qc_cmd_cl     weapon_find          ${* ?}" // Show spawn locations of a weapon
+
 alias exit                 "quit"
 
 // other aliases for local commands
@@ -147,21 +161,21 @@ seta cl_autoswitch 1 "automatically switch to newly picked up weapons if they ar
 // commented out commands are really only intended for internal use, or already have declaration in the engine
 alias autoswitch           "qc_cmd_cmd    autoswitch           ${* ?}" // Whether or not to switch automatically when getting a better weapon
 alias clientversion        "qc_cmd_cmd    clientversion        ${* ?}" // Release version of the game
-//alias mv_getpicture      "qc_cmd_cmd    mv_getpicture        ${* ?}" // Retrieve mapshot picture from the server
 alias join                 "qc_cmd_cmd    join                 ${* ?}" // Become a player in the game
+alias minigame             "qc_cmd_cmd    minigame             ${* ?}" // Start a minigame
+//alias mv_getpicture      "qc_cmd_cmd    mv_getpicture        ${* ?}" // Retrieve mapshot picture from the server
+alias physics              "qc_cmd_cmd    physics              ${* ?}" // Change physics set
 alias ready                "qc_cmd_cmd    ready                ${* ?}" // Qualify as ready to end warmup stage (or restart server if allowed)
-alias reportcvar           "qc_cmd_cmd    reportcvar           ${* ?}" // Old system for sending a client cvar to the server
 //alias say                "qc_cmd_cmd    say                  ${* ?}" // Print a message to chat to all players
 //alias say_team           "qc_cmd_cmd    say_team             ${* ?}" // Print a message to chat to all team mates
 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    editmob              ${* ?}" // Edit a monster's properties
-alias physics              "qc_cmd_cmd    physics              ${* ?}" // Change physics set
 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
 alias voice                "qc_cmd_cmd    voice                ${* ?}" // Send voice message via sound
+alias wpeditor             "qc_cmd_cmd    wpeditor             ${* ?}" // Waypoint editor commands
 
 // other aliases for client-to-server commands
 alias autoswitch "set cl_autoswitch ${1 ?} ; cmd autoswitch ${1 ?}" // todo
@@ -194,6 +208,7 @@ alias mobbutcher "editmob butcher ${* ?}"
 alias adminmsg             "qc_cmd_sv     adminmsg             ${* ?}" // Send an admin message to a client directly
 alias allready             "qc_cmd_sv     allready             ${* ?}" // Restart the server and reset the players
 alias allspec              "qc_cmd_sv     allspec              ${* ?}" // Force all players to spectate
+alias animbench            "qc_cmd_sv     animbench            ${* ?}" // Benchmark model animation (LAGS)
 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
@@ -204,7 +219,6 @@ alias defer_clear_all      "qc_cmd_sv     defer_clear_all      ${* ?}" // Clear
 alias delrec               "qc_cmd_sv     delrec               ${* ?}" // Delete race time record for a map
 alias effectindexdump      "qc_cmd_sv     effectindexdump      ${* ?}" // Dump list of effects from code and effectinfo.txt
 alias extendmatchtime      "qc_cmd_sv     extendmatchtime      ${* ?}" // Increase the timelimit value incrementally
-alias find                 "qc_cmd_sv     find                 ${* ?}" // Search through entities for matching classname
 alias gametype             "qc_cmd_sv     gametype             ${* ?}" // Simple command to change the active gametype
 alias gettaginfo           "qc_cmd_sv     gettaginfo           ${* ?}" // Get specific information about a weapon model
 alias gotomap              "qc_cmd_sv     gotomap              ${* ?}" // Simple command to switch to another map
index 9d4f8d03d3b8cbb46488d04674fecc3288fd1b91..2423e3782526671792bb23d8d4049a07e7971f1f 100644 (file)
@@ -24,7 +24,7 @@ msgstr ""
 "Project-Id-Version: Xonotic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-07-07 11:33+0000\n"
+"PO-Revision-Date: 2018-07-13 16:04+0000\n"
 "Last-Translator: Wuzzy <almikes@aol.com>\n"
 "Language-Team: German (http://www.transifex.com/team-xonotic/xonotic/"
 "language/de/)\n"
@@ -222,7 +222,7 @@ msgstr "Persönliche Bestzeit"
 
 #: qcsrc/client/hud/panel/modicons.qc:576
 msgid "Server best"
-msgstr "Server Bestzeit"
+msgstr "Server-Bestzeit"
 
 #: qcsrc/client/hud/panel/notify.qc:115 qcsrc/client/hud/panel/notify.qc:116
 #: qcsrc/client/hud/panel/score.qc:59
@@ -1070,11 +1070,11 @@ msgstr "Keine Munition mehr"
 
 #: qcsrc/client/hud/panel/weapons.qc:534
 msgid "Don't have"
-msgstr "Nicht vorhanden"
+msgstr "Hast du nicht"
 
 #: qcsrc/client/hud/panel/weapons.qc:538
 msgid "Unavailable"
-msgstr "Nicht verfügbar"
+msgstr "Fehlend"
 
 #: qcsrc/client/main.qc:1014
 msgid " qu/s"
@@ -2734,12 +2734,12 @@ msgstr "^BGTeam ^TC^TT^BG hielt den Ball zu lange fest"
 #: qcsrc/common/notifications/all.inc:412
 #, c-format
 msgid "^BG%s^BG captured %s^BG control point"
-msgstr "^BG%s^BG hat den Kontrollpunkt von %s^BG erobert"
+msgstr "^BG%s^BG hat einen Kontrollpunkt erobert: %s^BG"
 
 #: qcsrc/common/notifications/all.inc:413
 #, c-format
 msgid "^TC^TT^BG team %s^BG control point has been destroyed by %s"
-msgstr "^TC^TT^BGDer Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
+msgstr "^TC^TT^BGEin Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
 
 #: qcsrc/common/notifications/all.inc:414
 msgid "^TC^TT^BG generator has been destroyed"
@@ -3913,12 +3913,12 @@ msgstr "^F2Aktive Waffe: ^F1%s"
 #: qcsrc/common/notifications/all.inc:733
 #, c-format
 msgid "^BGYou captured %s^BG control point"
-msgstr "^BGDu hast den Kontrollpunkt von %s^BG erobert"
+msgstr "^BGDu hast einen Kontrollpunkt erobert: %s^BG"
 
 #: qcsrc/common/notifications/all.inc:734
 #, c-format
 msgid "^TC^TT^BG team captured %s^BG control point"
-msgstr "Team ^TC^TT^BG hat %ss^BG Kontrollpunkt erobert"
+msgstr "Team ^TC^TT^BG hat einen Kontrollpunkt erobert: %s^BG"
 
 #: qcsrc/common/notifications/all.inc:735
 msgid "^BGThis control point currently cannot be captured"
index 0ddbcc7bcb34b3c9f11867563ccdf277ef3a73b0..2c2c96b5dace8b9b653ec744dc4139c7baba613c 100644 (file)
@@ -24,7 +24,7 @@ msgstr ""
 "Project-Id-Version: Xonotic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-07-07 11:33+0000\n"
+"PO-Revision-Date: 2018-07-13 16:04+0000\n"
 "Last-Translator: Wuzzy <almikes@aol.com>\n"
 "Language-Team: German (http://www.transifex.com/team-xonotic/xonotic/"
 "language/de/)\n"
@@ -222,7 +222,7 @@ msgstr "Persönliche Bestzeit"
 
 #: qcsrc/client/hud/panel/modicons.qc:576
 msgid "Server best"
-msgstr "Server Bestzeit"
+msgstr "Server-Bestzeit"
 
 #: qcsrc/client/hud/panel/notify.qc:115 qcsrc/client/hud/panel/notify.qc:116
 #: qcsrc/client/hud/panel/score.qc:59
@@ -1070,11 +1070,11 @@ msgstr "Keine Munition mehr"
 
 #: qcsrc/client/hud/panel/weapons.qc:534
 msgid "Don't have"
-msgstr "Nicht vorhanden"
+msgstr "Hast du nicht"
 
 #: qcsrc/client/hud/panel/weapons.qc:538
 msgid "Unavailable"
-msgstr "Nicht verfügbar"
+msgstr "Fehlend"
 
 #: qcsrc/client/main.qc:1014
 msgid " qu/s"
@@ -2734,12 +2734,12 @@ msgstr "^BGTeam ^TC^TT^BG hielt den Ball zu lange fest"
 #: qcsrc/common/notifications/all.inc:412
 #, c-format
 msgid "^BG%s^BG captured %s^BG control point"
-msgstr "^BG%s^BG hat den Kontrollpunkt von %s^BG erobert"
+msgstr "^BG%s^BG hat einen Kontrollpunkt erobert: %s^BG"
 
 #: qcsrc/common/notifications/all.inc:413
 #, c-format
 msgid "^TC^TT^BG team %s^BG control point has been destroyed by %s"
-msgstr "^TC^TT^BGDer Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
+msgstr "^TC^TT^BGEin Kontrollpunkt vom Team %s^BG wurde von %s zerstört"
 
 #: qcsrc/common/notifications/all.inc:414
 msgid "^TC^TT^BG generator has been destroyed"
@@ -3914,12 +3914,12 @@ msgstr "^F2Aktive Waffe: ^F1%s"
 #: qcsrc/common/notifications/all.inc:733
 #, c-format
 msgid "^BGYou captured %s^BG control point"
-msgstr "^BGDu hast den Kontrollpunkt von %s^BG erobert"
+msgstr "^BGDu hast einen Kontrollpunkt erobert: %s^BG"
 
 #: qcsrc/common/notifications/all.inc:734
 #, c-format
 msgid "^TC^TT^BG team captured %s^BG control point"
-msgstr "Team ^TC^TT^BG hat %ss^BG Kontrollpunkt erobert"
+msgstr "Team ^TC^TT^BG hat einen Kontrollpunkt erobert: %s^BG"
 
 #: qcsrc/common/notifications/all.inc:735
 msgid "^BGThis control point currently cannot be captured"
index 6aa36e0816d98228fe28624b55ca4e0c79bf5594..f158cef5b8f5c2a755f0310dc7c1978e4836a448 100644 (file)
@@ -9,7 +9,7 @@ msgstr ""
 "Project-Id-Version: Xonotic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-07-10 10:55+0000\n"
+"PO-Revision-Date: 2018-07-18 12:01+0000\n"
 "Last-Translator: nad le <nadavlevi726@gmail.com>\n"
 "Language-Team: Hebrew (http://www.transifex.com/team-xonotic/xonotic/"
 "language/he/)\n"
@@ -1655,7 +1655,7 @@ msgstr ""
 
 #: qcsrc/common/mutators/mutator/nades/nades.qh:32
 msgid "Grenade"
-msgstr ""
+msgstr "רימון"
 
 #: qcsrc/common/mutators/mutator/overkill/hmg.qh:17
 msgid "Heavy Machine Gun"
@@ -1675,15 +1675,15 @@ msgstr ""
 
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:5
 msgid "Here"
-msgstr ""
+msgstr "כאן"
 
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:6
 msgid "DANGER"
-msgstr ""
+msgstr "סכנה"
 
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:8
 msgid "Frozen!"
-msgstr ""
+msgstr "קפוא!"
 
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:10
 msgid "Item"
@@ -1777,7 +1777,7 @@ msgstr ""
 
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:39
 msgid "Run here"
-msgstr ""
+msgstr "רוץ לכאן"
 
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:45
 #: qcsrc/common/mutators/mutator/waypoints/all.inc:48
@@ -7942,7 +7942,7 @@ msgstr "עכבר"
 
 #: qcsrc/menu/xonotic/dialog_settings_input.qc:59
 msgid "Sensitivity:"
-msgstr ""
+msgstr "רגישות:"
 
 #: qcsrc/menu/xonotic/dialog_settings_input.qc:61
 msgid "Mouse speed multiplier"
@@ -7992,7 +7992,7 @@ msgstr ""
 
 #: qcsrc/menu/xonotic/dialog_settings_input.qc:96
 msgid "Automatically repeat jumping if holding jump"
-msgstr ""
+msgstr "המשך לקפוץ באופן אוטומטי אם מקש קפיצה לחוץ"
 
 #: qcsrc/menu/xonotic/dialog_settings_input.qc:99
 msgid "Jetpack on jump:"
@@ -8482,7 +8482,7 @@ msgstr ""
 
 #: qcsrc/menu/xonotic/dialog_settings_video.qc:119
 msgid "Brightness:"
-msgstr ""
+msgstr "בהירות:"
 
 #: qcsrc/menu/xonotic/dialog_settings_video.qc:121
 msgid "Brightness of black (default: 0)"
@@ -8490,7 +8490,7 @@ msgstr ""
 
 #: qcsrc/menu/xonotic/dialog_settings_video.qc:123
 msgid "Contrast:"
-msgstr ""
+msgstr "ניגודיות:"
 
 #: qcsrc/menu/xonotic/dialog_settings_video.qc:125
 msgid "Brightness of white (default: 1)"
@@ -8858,7 +8858,7 @@ msgstr ""
 
 #: qcsrc/menu/xonotic/serverlist.qc:763
 msgid "Ping"
-msgstr ""
+msgstr "פינג"
 
 #: qcsrc/menu/xonotic/serverlist.qc:764
 msgid "Hostname"
@@ -8870,7 +8870,7 @@ msgstr "מפה"
 
 #: qcsrc/menu/xonotic/serverlist.qc:766
 msgid "Type"
-msgstr ""
+msgstr "סוג"
 
 #: qcsrc/menu/xonotic/serverlist.qc:1060
 #, c-format
@@ -9006,6 +9006,9 @@ msgid ""
 "texture memory usage, but make the textures appear very blurry. (default: "
 "good)"
 msgstr ""
+"שנה את החדות של טקסטורות.\n"
+"הנמכה תגרום לצמצום יעיל של זיכרון שמשומש על ידי טקסטורות, אבל יגרום "
+"לטקסטורות להראות מאוד מטושטשות. (ברירת מחדל: טוב)"
 
 #: qcsrc/menu/xonotic/slider_resolution.qc:115
 msgid "Screen resolution"
@@ -9077,7 +9080,7 @@ msgstr ""
 
 #: qcsrc/menu/xonotic/statslist.qc:103
 msgid "Last_Seen:"
-msgstr ""
+msgstr "נראה_לאחרונה:"
 
 #: qcsrc/menu/xonotic/statslist.qc:110
 msgid "Time_Played:"
index 605d51a6fc4ff7439c391bbe8ce2f429871ae3c7..2951cdd5f599c309dcb0d83e14502cf653810ee0 100644 (file)
@@ -5,15 +5,17 @@
 # Translators:
 # Ákos RUSZKAI, 2012
 # divVerent <divVerent@xonotic.org>, 2011
+# MmAaXx500 <viktor.balogh2000@gmail.com>, 2018
 # Peter Ferenczy <fpeterhu@gmail.com>, 2017
 # divVerent <divVerent@xonotic.org>, 2011
+# MmAaXx500 <viktor.balogh2000@gmail.com>, 2018
 msgid ""
 msgstr ""
 "Project-Id-Version: Xonotic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2017-09-19 19:55+0000\n"
-"Last-Translator: divVerent <divVerent@xonotic.org>\n"
+"PO-Revision-Date: 2018-08-26 13:48+0000\n"
+"Last-Translator: MmAaXx500 <viktor.balogh2000@gmail.com>\n"
 "Language-Team: Hungarian (http://www.transifex.com/team-xonotic/xonotic/"
 "language/hu/)\n"
 "Language: hu\n"
@@ -380,12 +382,12 @@ msgstr ""
 #: qcsrc/client/hud/panel/quickmenu.qc:823
 #: qcsrc/client/hud/panel/quickmenu.qc:860
 msgid "QMCMD^Settings"
-msgstr ""
+msgstr "QMCMD^Beállítások"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:824
 #: qcsrc/client/hud/panel/quickmenu.qc:831
 msgid "QMCMD^View/HUD settings"
-msgstr ""
+msgstr "QMCMD^Nézet/HUD beállítások"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:825
 msgid "QMCMD^3rd person view"
@@ -397,49 +399,49 @@ msgstr ""
 
 #: qcsrc/client/hud/panel/quickmenu.qc:827
 msgid "QMCMD^Names above players"
-msgstr ""
+msgstr "QMCMD^Nevek a játékosok fölött"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:828
 msgid "QMCMD^Crosshair per weapon"
-msgstr ""
+msgstr "QMCMD^Célkereszt fegyverenként"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:829
 msgid "QMCMD^FPS"
-msgstr ""
+msgstr "QMCMD^FPS"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:830
 msgid "QMCMD^Net graph"
-msgstr ""
+msgstr "QMCMD^Hálózati grafikon"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:833
 #: qcsrc/client/hud/panel/quickmenu.qc:836
 msgid "QMCMD^Sound settings"
-msgstr ""
+msgstr "Hang beállítások"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:834
 msgid "QMCMD^Hit sound"
-msgstr ""
+msgstr "QMCMD^Találat hang"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:835
 msgid "QMCMD^Chat sound"
-msgstr ""
+msgstr "QMCMD^Chat hang"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:840
 #: qcsrc/client/hud/panel/quickmenu.qc:844
 msgid "QMCMD^Spectator camera"
-msgstr ""
+msgstr "QMCMD^Szemlélő kamera"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:841
 msgid "QMCMD^1st person"
-msgstr ""
+msgstr "QMCMD^Első személy"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:842
 msgid "QMCMD^3rd person around player"
-msgstr ""
+msgstr "QMCMD^Harmadik személy a játékos körül"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:843
 msgid "QMCMD^3rd person behind"
-msgstr ""
+msgstr "QMCMD^Harmadik személy mögött"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:849
 #: qcsrc/client/hud/panel/quickmenu.qc:854
@@ -448,11 +450,11 @@ msgstr ""
 
 #: qcsrc/client/hud/panel/quickmenu.qc:850
 msgid "QMCMD^Increase speed"
-msgstr ""
+msgstr "QMCMD^Sebesség növelése"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:851
 msgid "QMCMD^Decrease speed"
-msgstr ""
+msgstr "QMCMD^Sebesség csökkentése"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:852
 msgid "QMCMD^Wall collision off"
@@ -464,36 +466,36 @@ msgstr ""
 
 #: qcsrc/client/hud/panel/quickmenu.qc:857
 msgid "QMCMD^Fullscreen"
-msgstr ""
+msgstr "QMCMD^Teljes képernyő"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:859
 msgid "QMCMD^Translate chat messages"
-msgstr ""
+msgstr "QMCMD^Chat üzenetek lefordítása"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:862
 #: qcsrc/client/hud/panel/quickmenu.qc:872
 msgid "QMCMD^Call a vote"
-msgstr ""
+msgstr "QMCMD^Szavazás indítása"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:863
 msgid "QMCMD^Restart the map"
-msgstr ""
+msgstr "QMCMD^Játék újraindítása"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:864
 msgid "QMCMD^End match"
-msgstr ""
+msgstr "QMCMD^Játék vége"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:867
 msgid "QMCMD^Reduce match time"
-msgstr ""
+msgstr "QMCMD^Játékidő csökkentése"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:868
 msgid "QMCMD^Extend match time"
-msgstr ""
+msgstr "QMCMD^Játékidő csökkentése"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:871
 msgid "QMCMD^Shuffle teams"
-msgstr ""
+msgstr "QMCMD^Csapatok összekeverése"
 
 #: qcsrc/client/hud/panel/racetimer.qc:37
 #, c-format
@@ -561,7 +563,7 @@ msgstr "megsemmisítve"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:84
 msgid "SCO^damage"
-msgstr ""
+msgstr "SCO^sérülés"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:85
 msgid "SCO^dmgtaken"
@@ -621,7 +623,7 @@ msgstr "Név"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:99
 msgid "SCO^sum"
-msgstr ""
+msgstr "SCO^össz"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:100
 msgid "SCO^nick"
@@ -931,7 +933,7 @@ msgstr "Feldezett titkok:"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:1354
 msgid "Capture time rankings"
-msgstr ""
+msgstr "Célbaérési idő rangsor"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:1354
 msgid "Rankings"
@@ -1002,7 +1004,7 @@ msgstr " amíg valaki ^3%s %s^7 -ig nem vezeti a mezőnyt."
 #: qcsrc/client/hud/panel/scoreboard.qc:1688
 #, c-format
 msgid "^1Respawning in ^3%s^1..."
-msgstr ""
+msgstr "^1Respawning: ^3%s^1..."
 
 #: qcsrc/client/hud/panel/scoreboard.qc:1698
 #, c-format
index c3b8efc999459771b73f37b4db9e59c5e44f3117..7306a0dca6201651fca866d10660c7a7ae5b03c9 100644 (file)
@@ -14,7 +14,7 @@ msgstr ""
 "Project-Id-Version: Xonotic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-05-22 14:42+0000\n"
+"PO-Revision-Date: 2018-07-22 18:03+0000\n"
 "Last-Translator: Jean Trindade Pereira <jean_trindade2@hotmail.com>\n"
 "Language-Team: Portuguese (Brazil) (http://www.transifex.com/team-xonotic/"
 "xonotic/language/pt_BR/)\n"
@@ -27,7 +27,7 @@ msgstr ""
 #: qcsrc/client/hud/hud_config.qc:239
 #, c-format
 msgid "^2Successfully exported to %s! (Note: It's saved in data/data/)\n"
-msgstr "^2Exportado com sucesso para %s! (Nota: Foi salvo em data/data/)\n"
+msgstr "^2Exportado com sucesso para %s! (Nota: foi salvo em data/data/)\n"
 
 #: qcsrc/client/hud/hud_config.qc:243
 #, c-format
@@ -155,12 +155,11 @@ msgstr "%sAperte ^3%s%s assim que estiver pronto"
 #: qcsrc/client/hud/panel/infomessages.qc:167
 msgid "^2Waiting for others to ready up to end warmup..."
 msgstr ""
-"^2Esperando que os outros jogadores estejam prontos para acabar o "
-"aquecimento..."
+"^2Aguardando outros jogadores ficarem prontos para terminar o aquecimento..."
 
 #: qcsrc/client/hud/panel/infomessages.qc:169
 msgid "^2Waiting for others to ready up..."
-msgstr "^2Esperando que os outros jogadores estejam prontos..."
+msgstr "^2Aguardando outros jogadores ficarem prontos..."
 
 #: qcsrc/client/hud/panel/infomessages.qc:175
 #, c-format
@@ -183,7 +182,7 @@ msgstr "menu de equipe"
 
 #: qcsrc/client/hud/panel/infomessages.qc:209
 msgid "^1Spectating this player:"
-msgstr "^1Também estão assistindo a este jogador:"
+msgstr "^1Assistindo a este jogador:"
 
 #: qcsrc/client/hud/panel/infomessages.qc:209
 msgid "^1Spectating you:"
@@ -191,7 +190,7 @@ msgstr "^1Assistindo você:"
 
 #: qcsrc/client/hud/panel/infomessages.qc:225
 msgid "^7Press ^3ESC ^7to show HUD options."
-msgstr "^7Aperte ^3ESC ^7para exibir as opções de HUD."
+msgstr "^7Aperte ^3ESC ^7para exibir as opções de interface."
 
 #: qcsrc/client/hud/panel/infomessages.qc:226
 msgid "^3Doubleclick ^7a panel for panel-specific options."
@@ -201,7 +200,7 @@ msgstr ""
 
 #: qcsrc/client/hud/panel/infomessages.qc:227
 msgid "^3CTRL ^7to disable collision testing, ^3SHIFT ^7and"
-msgstr "^3CTRL ^7para desligar teste de colisão, ^3SHIFT ^7e"
+msgstr "Use ^3CTRL ^7para desligar o teste de colisão, e ^3SHIFT ^7e"
 
 #: qcsrc/client/hud/panel/infomessages.qc:228
 msgid "^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments."
@@ -365,7 +364,7 @@ msgstr "Largar arma, ícone"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:816
 msgid "QMCMD^dropped weapon %w^7 (l:%l^7)"
-msgstr "Arma solta %w^7 (l:%l^7)"
+msgstr "Arma largada %w^7 (l:%l^7)"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:817
 msgid "QMCMD^drop flag/key, icon"
@@ -377,7 +376,7 @@ msgstr "Bandeira/Chave largada %w^7 (l:%l^7)"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:821
 msgid "QMCMD^Send private message to"
-msgstr "Mandar mensagem privada para"
+msgstr "Enviar mensagem privada para"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:823
 #: qcsrc/client/hud/panel/quickmenu.qc:860
@@ -387,7 +386,7 @@ msgstr "Configurações"
 #: qcsrc/client/hud/panel/quickmenu.qc:824
 #: qcsrc/client/hud/panel/quickmenu.qc:831
 msgid "QMCMD^View/HUD settings"
-msgstr "Configurações de Exibição/HUD"
+msgstr "Configurações de exibição/interface"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:825
 msgid "QMCMD^3rd person view"
@@ -454,7 +453,7 @@ msgstr "Aumentar velocidade"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:851
 msgid "QMCMD^Decrease speed"
-msgstr "Diminuir velocidade"
+msgstr "Reduzir velocidade"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:852
 msgid "QMCMD^Wall collision off"
@@ -685,7 +684,7 @@ msgstr "ticks"
 msgid ""
 "You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"
 msgstr ""
-"Você pode modificar o placar usando o comando ^2scoreboard_columns_set.\n"
+"É possível modificar o placar usando o comando ^2scoreboard_columns_set.\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:296
 msgid "^3|---------------------------------------------------------------|\n"
@@ -701,13 +700,13 @@ msgstr "^2scoreboard_columns_set padrão\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:299
 msgid "^2scoreboard_columns_set ^7field1 field2 ...\n"
-msgstr "^2scoreboard_columns_set ^7campo1 campo2...\n"
+msgstr "^2scoreboard_columns_set ^7field1 field2...\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:300
 msgid "The following field names are recognized (case insensitive):\n"
 msgstr ""
-"Os seguintes nomes de campos são reconhecidos (maiúsculas/minúsculas não são "
-"distintas):\n"
+"Os seguintes nomes de campo são reconhecidos (maiúsculas e minúsculas não "
+"diferem):\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:301
 msgid "You can use a ^3|^7 to start the right-aligned fields.\n"
@@ -731,7 +730,7 @@ msgstr "^3elo^7 ELO do jogador\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:308
 msgid "^3kills^7                    Number of kills\n"
-msgstr "^3vítimas^7 Número de aniquilações\n"
+msgstr "^3vítimas^7 Número de vítimas\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:309
 msgid "^3deaths^7                   Number of deaths\n"
@@ -783,8 +782,7 @@ msgstr "^3tempodecaptura^7 Tempo da captura mais rápida (CTF)\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:319
 msgid "^3fckills^7                  Number of flag carrier kills\n"
-msgstr ""
-"^3pbndvítimas^7 Número de portadores de bandeiras mortos pelo jogador\n"
+msgstr "^3pbndvítimas^7 Número de portadores de bandeiras aniquilados\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:320
 msgid "^3returns^7                  Number of flag returns\n"
@@ -792,7 +790,7 @@ msgstr "^3retornos^7 Número de bandeiras retomadas\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:321
 msgid "^3drops^7                    Number of flag drops\n"
-msgstr "^3quedas^7 Número de bandeiras que foram soltas\n"
+msgstr "^3quedas^7 Número de bandeiras largadas\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:322
 msgid "^3lives^7                    Number of lives (LMS)\n"
@@ -804,18 +802,18 @@ msgstr "^3posição^7 Classificação do jogador\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:324
 msgid "^3pushes^7                   Number of players pushed into void\n"
-msgstr "^3empurrões^7 Número de jogadores empurrados para fora do mapa\n"
+msgstr "^3empurrões^7 Número de jogadores empurrados para o vazio\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:325
 msgid ""
 "^3destroyed^7                Number of keys destroyed by pushing them into "
 "void\n"
 msgstr ""
-"^3destruídas^7 Número de chaves destruídas ao empurrá-las para fora do mapa\n"
+"^3destruídas^7 Número de chaves destruídas ao empurrá-las para o vazio\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:326
 msgid "^3kckills^7                  Number of keys carrier kills\n"
-msgstr "^3pcvítimas^7 Número de portadores de chaves mortos pelo jogador\n"
+msgstr "^3pcvítimas^7 Número de portadores de chaves aniquilados\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:327
 msgid "^3losses^7                   Number of times a key was lost\n"
@@ -827,7 +825,7 @@ msgstr "^3voltas^7 Número de voltas concluídas (corrida/cts)\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:329
 msgid "^3time^7                     Total time raced (race/cts)\n"
-msgstr "^3tempo^7 Tempo total de corrida (corrida/cts)\n"
+msgstr "^3tempo^7 Tempo total em corridas (corrida/cts)\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:330
 msgid "^3fastest^7                  Time of fastest lap (race/cts)\n"
@@ -843,7 +841,7 @@ msgstr "^3tomados^7 Número de pontos de dominação tomados (DOM)\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:333
 msgid "^3bckills^7                  Number of ball carrier kills\n"
-msgstr "^3pblvítimas^7 Número de portadores de bolas mortos pelo jogador\n"
+msgstr "^3pblvítimas^7 Número de portadores de bolas aniquilados\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:334
 msgid ""
@@ -1017,7 +1015,8 @@ msgstr "Você morreu. Aperte ^2%s^7 para ressurgir"
 #: qcsrc/client/hud/panel/vote.qc:24
 msgid "^1You must answer before entering hud configure mode\n"
 msgstr ""
-"^1Você tem que responder antes de entrar no modo de configuração do HUD\n"
+"^1Você tem que responder antes de entrar no modo de configuração da "
+"interface\n"
 
 #: qcsrc/client/hud/panel/vote.qc:29
 msgid "^2Name ^7instead of \"^1Anonymous player^7\" in stats"
@@ -1033,7 +1032,7 @@ msgstr "Permitir que servidores armazenem e mostrem o seu nome?"
 
 #: qcsrc/client/hud/panel/vote.qc:121
 msgid "^1Configure the HUD"
-msgstr "^1Configurar o HUD"
+msgstr "^1Configurar a interface"
 
 #: qcsrc/client/hud/panel/vote.qc:125 qcsrc/menu/xonotic/dialog_firstrun.qc:82
 #: qcsrc/menu/xonotic/dialog_multiplayer_media_demo_startconfirm.qc:18
@@ -5716,7 +5715,7 @@ msgstr "Painel das Armas"
 
 #: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:19
 msgid "HUD skins"
-msgstr "Visuais de HUD"
+msgstr "Visuais de interface"
 
 #: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:22
 #: qcsrc/menu/xonotic/dialog_multiplayer_create.qc:196
@@ -5783,7 +5782,7 @@ msgstr "Preenchimento:"
 
 #: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:93
 msgid "HUD Dock:"
-msgstr "Camada do HUD:"
+msgstr "Camada da interface:"
 
 #: qcsrc/menu/xonotic/dialog_hudsetup_exit.qc:95
 msgid "DOCK^Disabled"
@@ -5827,7 +5826,7 @@ msgstr "Sair da configuração"
 
 #: qcsrc/menu/xonotic/dialog_hudsetup_exit.qh:6
 msgid "Panel HUD Setup"
-msgstr "Painel de Configuração do HUD"
+msgstr "Painel de configuração da interface"
 
 #: qcsrc/menu/xonotic/dialog_monstertools.qc:13
 msgid "Monster:"
@@ -7754,32 +7753,33 @@ msgstr "Sobreposição do dano:"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:160
 msgid "Dynamic HUD"
-msgstr "HUD dinâmico"
+msgstr "Interface dinâmica"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:161
 msgid "HUD moves around following player's movement"
-msgstr "O HUD se move de acordo com o movimento do jogador"
+msgstr "A interface se move de acordo com o movimento do jogador"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:163
 msgid "Shake the HUD when hurt"
-msgstr "Vibrar o HUD ao ser atingido"
+msgstr "Vibrar a interface ao receber dano"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hud.qc:167
 #: qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.qh:6
 msgid "Enter HUD editor"
-msgstr "Entrar no editor do HUD"
+msgstr "Entrar no editor de interface"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hud.qh:7
 msgid "HUD"
-msgstr "HUD"
+msgstr "Interface"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.qc:21
 msgid "In order for the HUD editor to show, you must first be in game."
-msgstr "Para o editor do HUD aparecer, é necessário estar jogando em um mapa."
+msgstr ""
+"Para abrir o editor de interface, é necessário estar jogando em um mapa."
 
 #: qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.qc:23
 msgid "Do you wish to start a local game to set up the HUD?"
-msgstr "Quer iniciar um jogo local para personalizar o HUD?"
+msgstr "Quer iniciar um jogo local para personalizar a interface?"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:24
 msgid "Frag Information"
@@ -7888,7 +7888,7 @@ msgstr "Sons da contagem de ressurgimento"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:101
 msgid "Killstreak sounds"
-msgstr "Sons de sequências de mortes"
+msgstr "Sons de sequência de mortes"
 
 #: qcsrc/menu/xonotic/dialog_settings_game_messages.qc:104
 msgid "Achievement sounds"
index 06e1922ae77fc612fb754d24eb30aba42d40dd4f..7b2b73abe3433ff3980f6f67f8866ab75a310a71 100644 (file)
@@ -6,7 +6,7 @@
 # adem4ik, 2014
 # Alex Talker <alextalker7@gmail.com>, 2014-2015
 # Andrei Stepanov, 2014
-# Andrei Stepanov, 2014-2018
+# Andrei Stepanov <adem4ik@gmail.com>, 2014-2018
 # Andrey P <andrey.pyntikov@gmail.com>, 2016
 # Artem Vorotnikov <artem@vorotnikov.me>, 2015
 # Lord Canistra <lordcanistra@gmail.com>, 2011
@@ -19,7 +19,7 @@ msgstr ""
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
 "PO-Revision-Date: 2018-03-16 06:43+0000\n"
-"Last-Translator: Andrei Stepanov\n"
+"Last-Translator: Andrei Stepanov <adem4ik@gmail.com>\n"
 "Language-Team: Russian (http://www.transifex.com/team-xonotic/xonotic/"
 "language/ru/)\n"
 "Language: ru\n"
index 050f5f680ab9d1bf62be321dca7d5c12a480d102..669d4a45c58103f08a0aec4b745b93486e08c8dc 100644 (file)
@@ -15,7 +15,7 @@ msgstr ""
 "Project-Id-Version: Xonotic\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2017-07-09 00:35+0200\n"
-"PO-Revision-Date: 2018-06-02 05:39+0000\n"
+"PO-Revision-Date: 2018-09-02 02:29+0000\n"
 "Last-Translator: Losier Blackheath <losier.cc@gmail.com>\n"
 "Language-Team: Chinese (China) (http://www.transifex.com/team-xonotic/"
 "xonotic/language/zh_CN/)\n"
@@ -198,7 +198,7 @@ msgstr "^3双击 ^7面板以获取面板特定选项。"
 
 #: qcsrc/client/hud/panel/infomessages.qc:227
 msgid "^3CTRL ^7to disable collision testing, ^3SHIFT ^7and"
-msgstr ""
+msgstr "^3CTRL ^7以禁用碰撞检测, ^3SHIFT ^7以及"
 
 #: qcsrc/client/hud/panel/infomessages.qc:228
 msgid "^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments."
@@ -277,7 +277,7 @@ msgstr ""
 
 #: qcsrc/client/hud/panel/quickmenu.qc:805
 msgid "QMCMD^took item (l:%l^7)"
-msgstr ""
+msgstr "QMCMD^捡起物品 (l:%l^7)"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:805
 msgid "QMCMD^took item, icon"
@@ -455,11 +455,11 @@ msgstr "QMCMD^减少速度"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:852
 msgid "QMCMD^Wall collision off"
-msgstr ""
+msgstr "QMCMD^关闭墙壁碰撞"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:853
 msgid "QMCMD^Wall collision on"
-msgstr ""
+msgstr "QMCMD^开启墙壁碰撞"
 
 #: qcsrc/client/hud/panel/quickmenu.qc:857
 msgid "QMCMD^Fullscreen"
@@ -753,7 +753,7 @@ msgstr "^3dmgtaken^7                 遭受的总破坏值\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:315
 msgid "^3sum^7                      frags - deaths\n"
-msgstr ""
+msgstr "^3sum^7                      击杀数 - 死亡数\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:316
 msgid ""
@@ -767,6 +767,8 @@ msgid ""
 "^3pickups^7                  How often a flag (CTF) or a key (KeyHunt) or a "
 "ball (Keepaway) was picked up\n"
 msgstr ""
+"^3pickups^7                  旗帜 (CTF) 或钥匙 (KeyHunt) 或球 (Keepaway) 被捡"
+"起来\n"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:318
 msgid "^3captime^7                  Time of fastest cap (CTF)\n"
@@ -920,7 +922,7 @@ msgstr "计分板"
 #: qcsrc/client/hud/panel/scoreboard.qc:1584
 #, c-format
 msgid "Speed award: %d%s ^7(%s^7)"
-msgstr ""
+msgstr "速度奖励: %d%s ^7(%s^7)"
 
 #: qcsrc/client/hud/panel/scoreboard.qc:1588
 #, c-format
@@ -3167,7 +3169,7 @@ msgstr ""
 
 #: qcsrc/common/notifications/all.inc:559
 msgid "^BGYou captured the flag!"
-msgstr ""
+msgstr "^BG你捡到了旗帜!"
 
 #: qcsrc/common/notifications/all.inc:560
 #, c-format
@@ -3212,7 +3214,7 @@ msgstr ""
 #: qcsrc/common/notifications/all.inc:568
 #, c-format
 msgid "^BGYou passed the flag to %s"
-msgstr ""
+msgstr "^BG你把旗帜交给了 %s"
 
 #: qcsrc/common/notifications/all.inc:569
 msgid "^BGYou got the ^TC^TT^BG flag!"
index 128169c502911d760c7de4707850887c805f6284..c07774a05b99473c12eb20e5f9de754fc87f895c 100644 (file)
@@ -2751,14 +2751,36 @@ effect flac_explode
        velocityjitter 256 256 256
        velocityoffset 0 0 80
 effect tr_bullet
-       type spark
-       alpha 256 256 2560
-       color 0xff8960 0xff8533
-       size 4 4
-       stretchfactor 0.200000
-       tex 70 70
-       trailspacing 750
-       velocitymultiplier 3
+       type beam
+       alpha 500 600 10000
+       color 0xf03000 0xff6010
+       countabsolute 1
+       sizeincrease -3
+       size 0.6 0.8
+       tex 200 200
+effect tr_bullet
+       type smoke
+       airfriction -4
+       alpha 256 256 350
+       color 0x202020 0x404040
+       notunderwater
+       sizeincrease 0.400000
+       size 1 2
+       tex 0 8
+       trailspacing 16
+       velocityjitter 4 4 4
+effect tr_bullet
+       type bubble
+       alpha 256 256 128
+       bounce 1.500000
+       color 0x404040 0x808080
+       gravity -0.125000
+       liquidfriction 4
+       size 0.5 0.6
+       tex 62 62
+       trailspacing 16
+       underwater
+       velocityjitter 16 16 16
 effect smoking_smallemitter
        type alphastatic
        airfriction -1
@@ -8262,3 +8284,34 @@ effect arc_bolt_explode
        tex 40 40
        velocityjitter 224 224 224
        velocityoffset 0 0 80
+effect tr_bullet_weak
+       type beam
+       alpha 75 100 3000
+       color 0xf03000 0xff6010
+       countabsolute 1
+       sizeincrease -3
+       size 0.6 0.8
+       tex 200 200
+effect tr_bullet_weak
+       type smoke
+       airfriction -4
+       alpha 256 256 350
+       color 0x202020 0x404040
+       notunderwater
+       sizeincrease 0.400000
+       size 1 2
+       tex 0 8
+       trailspacing 16
+       velocityjitter 4 4 4
+effect tr_bullet_weak
+       type bubble
+       alpha 256 256 128
+       bounce 1.500000
+       color 0x404040 0x808080
+       gravity -0.125000
+       liquidfriction 4
+       size 0.5 0.6
+       tex 62 62
+       trailspacing 32
+       underwater
+       velocityjitter 16 16 16
index e7a1607f15c8274f10603d441f65f9be21a3133d..c43b9d1d3f2e6fe73fddbb33e8489670350edc4d 100644 (file)
@@ -31,6 +31,7 @@ alias cl_hook_gamestart_cts
 alias cl_hook_gamestart_ka
 alias cl_hook_gamestart_ft
 alias cl_hook_gamestart_inv
+alias cl_hook_gamestart_duel
 alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends
 alias cl_hook_shutdown
 alias cl_hook_activeweapon
index 41b5fc5dbe745dd85c02b81e6ad2b3ac6d2a574f..57c9f7b7086f2c8ce25ff9f25cf475c2664c334c 100644 (file)
@@ -28,6 +28,7 @@ alias sv_hook_gamestart_cts
 alias sv_hook_gamestart_ka
 alias sv_hook_gamestart_ft
 alias sv_hook_gamestart_inv
+alias sv_hook_gamestart_duel
 alias sv_hook_gamerestart
 alias sv_hook_gameend
 
@@ -38,8 +39,7 @@ alias sv_hook_gameend
 // These are called when the mode is switched via gametype vote screen,
 // earlier than gamestart hooks (useful for enabling per-gamemode mutators)
 // The _all hook is called before the specific one
-// here it sets g_maxplayers to undo what duel does
-alias sv_vote_gametype_hook_all "set g_maxplayers 0"
+alias sv_vote_gametype_hook_all
 alias sv_vote_gametype_hook_as
 alias sv_vote_gametype_hook_ca
 alias sv_vote_gametype_hook_ctf
@@ -55,15 +55,17 @@ alias sv_vote_gametype_hook_nb
 alias sv_vote_gametype_hook_ons
 alias sv_vote_gametype_hook_rc
 alias sv_vote_gametype_hook_tdm
+alias sv_vote_gametype_hook_duel
 
-// Preset to allow duel to be used for the gametype voting screen
+// Example preset to allow 1v1ctf to be used for the gametype voting screen
 // sv_vote_gametype_*_type Must be set to the name of the gametype the option is based on
 // sv_vote_gametype_*_name Contains a human-readable name of the gametype
 // sv_vote_gametype_*_description Contains a longer description
-set sv_vote_gametype_duel_type dm
-set sv_vote_gametype_duel_name Duel
-set sv_vote_gametype_duel_description "One vs One match"
-alias sv_vote_gametype_hook_duel "set g_maxplayers 2"
+//set sv_vote_gametype_1v1ctf_type ctf
+//set sv_vote_gametype_1v1ctf_name "Capture the Flag Duel"
+//set sv_vote_gametype_1v1ctf_description "One vs One match in CTF"
+//alias sv_vote_gametype_hook_all "set g_maxplayers 0"
+//alias sv_vote_gametype_hook_1v1ctf "set g_maxplayers 2"
 
 
 // ===========
@@ -196,6 +198,13 @@ set g_inv_respawn_delay_large_count 0
 set g_inv_respawn_delay_max 0
 set g_inv_respawn_waves 0
 set g_inv_weapon_stay 0
+set g_duel_respawn_delay_small 0
+set g_duel_respawn_delay_small_count 0
+set g_duel_respawn_delay_large 0
+set g_duel_respawn_delay_large_count 0
+set g_duel_respawn_delay_max 0
+set g_duel_respawn_waves 0
+set g_duel_weapon_stay 0
 
 
 // =========
@@ -309,12 +318,15 @@ set g_cts 0 "CTS: complete the stage"
 set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts"
 set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible"
 set g_cts_send_rankings_cnt 15 "send this number of map records to clients"
+set g_cts_removeprojectiles 0 "remove projectiles when the player dies, to prevent using weapons earlier in the stage than intended"
 
 
 // ==========================
 //  deathmatch (ffa or team)
 // ==========================
 set g_dm 1 "Deathmatch: killing any other player is one frag, player with most frags wins"
+set g_tdm 0 "Team Deathmatch: the team who kills their opponents most often wins"
+set g_tdm_on_dm_maps 0 "when this is set, all DM maps automatically support TDM"
 set g_tdm_teams 2 "how many teams are in team deathmatch (set by mapinfo)"
 set g_tdm_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
 seta g_tdm_teams_override 0    "how many teams are in team deathmatch"
@@ -523,3 +535,10 @@ set g_invasion_spawnpoint_spawn_delay 0.5
 set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
 set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
 set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)"
+
+// ======
+//  duel
+// ======
+set g_duel 0 "Duel: frag the opponent more in a one versus one arena battle"
+//set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel"
+set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode"
index c36c3f5e7e587989e23d5d7db7b0e5039f216d75..cb0414cc1ea32fb1881fa8de3f13bbca501de60f 100644 (file)
Binary files a/gfx/hud/luma/weaponhmg.tga and b/gfx/hud/luma/weaponhmg.tga differ
index 643c5cafee679e92a84f4a60c28849cac75eb62e..74019d86f9ec8d079f59badbb83cd17a2afbd9b9 100644 (file)
Binary files a/gfx/hud/luma/weaponrpc.tga and b/gfx/hud/luma/weaponrpc.tga differ
index 3565f3c36e66741016aa6ac98e7534ffcd19baf3..49f3b7db3c3fa9958ce33eaae423c9f491e8e065 100644 (file)
Binary files a/gfx/menu/luma/bigbutton_c.tga and b/gfx/menu/luma/bigbutton_c.tga differ
index 0efae80e723d585a483a9892d43dd791915bc8a9..5fb89048098be4f3119826457c6e97f4fc843f28 100644 (file)
Binary files a/gfx/menu/luma/bigbutton_d.tga and b/gfx/menu/luma/bigbutton_d.tga differ
index 3edc7dab2f51ebae12b4a5f4936e42e95047ae8b..638dcd48752dfff421894772ccccb6eb901e0b90 100644 (file)
Binary files a/gfx/menu/luma/bigbutton_f.tga and b/gfx/menu/luma/bigbutton_f.tga differ
index 4f07b0c4be33cc64edc5863755e8d890b5c162a5..0c773b252bcd460293d2a79e8d05a71f5b184cbb 100644 (file)
Binary files a/gfx/menu/luma/bigbutton_n.tga and b/gfx/menu/luma/bigbutton_n.tga differ
index e18e0f6e7951c0ef450e46efbbb561b2e10433d5..eed1781f99d9536b4951a998d58c0c9d1d23da65 100644 (file)
Binary files a/gfx/menu/luma/bigbuttongray_c.tga and b/gfx/menu/luma/bigbuttongray_c.tga differ
index 87b5dd5286f464688a00bfea795986f8a708cc5a..8b6ef4be35ef5d1bac578cff139026cd3cfd0d07 100644 (file)
Binary files a/gfx/menu/luma/bigbuttongray_d.tga and b/gfx/menu/luma/bigbuttongray_d.tga differ
index 3624987845163f992ffbcca977fe669030da027f..b87d09474efd93891205ae344e2ded7544ba087a 100644 (file)
Binary files a/gfx/menu/luma/bigbuttongray_f.tga and b/gfx/menu/luma/bigbuttongray_f.tga differ
index e18e0f6e7951c0ef450e46efbbb561b2e10433d5..eed1781f99d9536b4951a998d58c0c9d1d23da65 100644 (file)
Binary files a/gfx/menu/luma/bigbuttongray_n.tga and b/gfx/menu/luma/bigbuttongray_n.tga differ
index 95bdc3c488526c385fd06c02c36070d9a22a536c..40705b6931fcdd4eb7e05cf9966f589bac305798 100644 (file)
Binary files a/gfx/menu/luma/border.tga and b/gfx/menu/luma/border.tga differ
index b86b63a25fc18c9e0aafc9bb5a74f8f07e9a7849..33c7cb74bbbae06f16b6a9e1cbcf8aefe4aea9eb 100644 (file)
Binary files a/gfx/menu/luma/button_c.tga and b/gfx/menu/luma/button_c.tga differ
index f80433038e859c215b6ec1cdd7a96ae1c42cf9ab..e5b65d7563773328ce48e30a6aa8e98ece7fcbbd 100644 (file)
Binary files a/gfx/menu/luma/button_d.tga and b/gfx/menu/luma/button_d.tga differ
index 0b54fee3403c6556c166cd8e8387181f7c0f9ffc..30d32521a50e75e20b115248849db2f5c9a740dc 100644 (file)
Binary files a/gfx/menu/luma/button_f.tga and b/gfx/menu/luma/button_f.tga differ
index 0dc4fd6af454d300bd713ce577774c96f4484e35..6d098576a3e6c4dbf8405c56b3619bf5b516fee6 100644 (file)
Binary files a/gfx/menu/luma/button_n.tga and b/gfx/menu/luma/button_n.tga differ
index 4c1ae7c0fef5412c24d9caed78455d95a465f072..d05266b40f82636516e219d203237097a75d997b 100644 (file)
Binary files a/gfx/menu/luma/buttongray_c.tga and b/gfx/menu/luma/buttongray_c.tga differ
index e0e8318e00b3d34c347f96e5e72a6c19ad05ba69..4bfab65e9954c469c6393151a873854c8971456f 100644 (file)
Binary files a/gfx/menu/luma/buttongray_d.tga and b/gfx/menu/luma/buttongray_d.tga differ
index cc604bc824b5aca5c947a7c34162862d7d2ef7c7..d5ac917fda07b8c2ada1b214e584c317383d8ff8 100644 (file)
Binary files a/gfx/menu/luma/buttongray_f.tga and b/gfx/menu/luma/buttongray_f.tga differ
index 4c1ae7c0fef5412c24d9caed78455d95a465f072..d05266b40f82636516e219d203237097a75d997b 100644 (file)
Binary files a/gfx/menu/luma/buttongray_n.tga and b/gfx/menu/luma/buttongray_n.tga differ
index f8e6960cc260e7d0a339625bd51c7d2c57d24e78..30669ca6af017b781c55a6bb737008ed465ea02a 100644 (file)
Binary files a/gfx/menu/luma/checkbox_c0.tga and b/gfx/menu/luma/checkbox_c0.tga differ
index 32d2f6d7b59f65039a9aebfd60c8f0768144b0ad..acb9bc609753ce82761cea0cbe8b0078bd825556 100644 (file)
Binary files a/gfx/menu/luma/checkbox_c1.tga and b/gfx/menu/luma/checkbox_c1.tga differ
index b059ab64c4a2112538367009cf88971d339bb18d..f42971a26acc918d01f1340d4485f404b1f62512 100644 (file)
Binary files a/gfx/menu/luma/checkbox_d0.tga and b/gfx/menu/luma/checkbox_d0.tga differ
index e38b9629ceb145fbb77cb06e1c17df96b3556ee5..2346dc73875973c96c18329ec30749ec5d449b59 100644 (file)
Binary files a/gfx/menu/luma/checkbox_d1.tga and b/gfx/menu/luma/checkbox_d1.tga differ
index 5201675e6778e8c2c6f7b473b620df3a53e351dc..ddc0842f7664729e4de86bd9daf40121957c4c5f 100644 (file)
Binary files a/gfx/menu/luma/checkbox_f0.tga and b/gfx/menu/luma/checkbox_f0.tga differ
index 116b29c80d90deb1cc820b3bcf720642334ffc44..fb16d9055b29dbddd3e15f79cf80c9ecb6d0ad61 100644 (file)
Binary files a/gfx/menu/luma/checkbox_f1.tga and b/gfx/menu/luma/checkbox_f1.tga differ
index 9786dc84ed431a56db68ed4fc4343c7e1630ea33..4f4d7120b914e494f1743210b7c24e9ee96c2926 100644 (file)
Binary files a/gfx/menu/luma/checkbox_n0.tga and b/gfx/menu/luma/checkbox_n0.tga differ
index 427d3ce421dffb55ae2c2af3f69d5dcdb0df88cc..1102900dacb5cb4abe360629ddfeebbab958ee5c 100644 (file)
Binary files a/gfx/menu/luma/checkbox_n1.tga and b/gfx/menu/luma/checkbox_n1.tga differ
index eb4b259fcd8dde88d69b43d143be91cd2531bfab..c6698a2c20108291d725ed090680845403ee1941 100644 (file)
Binary files a/gfx/menu/luma/checkmark.tga and b/gfx/menu/luma/checkmark.tga differ
index 36fc40b80438e02f0c4bf7ecbec8445b628c6a62..efcf9b5a563c6d9d42312be3ddbe1c2c071ab8b6 100644 (file)
Binary files a/gfx/menu/luma/clearbutton_c.tga and b/gfx/menu/luma/clearbutton_c.tga differ
index c2418a40d38968035d37c271f81af407edc5deb0..2306d91456144ce1466478c798f1526825ae2447 100644 (file)
Binary files a/gfx/menu/luma/clearbutton_f.tga and b/gfx/menu/luma/clearbutton_f.tga differ
index 3ac282b6e94853fc28259dbea4c58ddc01e61a85..343ae6a173759e31c745a79d3c03af39df340288 100644 (file)
Binary files a/gfx/menu/luma/clearbutton_n.tga and b/gfx/menu/luma/clearbutton_n.tga differ
index 3a8f46fd45749cdc51be15eeadf42cb5194fc650..1e03c392db11049b16089e205d2c2b333a3478d1 100644 (file)
Binary files a/gfx/menu/luma/closebutton_c.tga and b/gfx/menu/luma/closebutton_c.tga differ
index 0b3933b6d30faa8da4517f38bde51f5b2870625c..7f1f5b58f4bca691ede99e0fbbec79d42c868a0f 100644 (file)
Binary files a/gfx/menu/luma/closebutton_f.tga and b/gfx/menu/luma/closebutton_f.tga differ
index 4ea4b1cc12d76e3ed0cb0618a9da1dd635f65706..0313efab7cf060faa50d87f6edf9b194b0749fc9 100644 (file)
Binary files a/gfx/menu/luma/closebutton_n.tga and b/gfx/menu/luma/closebutton_n.tga differ
index 91f343e70339192491eab3787fa99dd32f3a2662..086f9a7a3df6f5f2348214372c809fab0e9fe1e0 100644 (file)
Binary files a/gfx/menu/luma/colorbutton_c.tga and b/gfx/menu/luma/colorbutton_c.tga differ
index 0aa806b0418c56ed3bd97c0296d4f7cd6f73d89d..32dd33e47958c742f26ed175e007ed87032d7b4a 100644 (file)
Binary files a/gfx/menu/luma/colorbutton_f.tga and b/gfx/menu/luma/colorbutton_f.tga differ
index bcba096780ef13ad713664c38997e05e2023dcd5..ca128b7922b3e15b95f46c3b06ac55b27db0ade0 100644 (file)
Binary files a/gfx/menu/luma/colorbutton_n.tga and b/gfx/menu/luma/colorbutton_n.tga differ
index 6775942eeca3197348cf8eb13f29523321223271..30ec7f78a764ab7697a0d19014fa95510e12adb0 100644 (file)
Binary files a/gfx/menu/luma/colorpicker_m.tga and b/gfx/menu/luma/colorpicker_m.tga differ
index b12436a98378463de96600cc261219b18b738875..87a12b6f2c74789a7fa395b5a6bc67c5ddf0b408 100644 (file)
Binary files a/gfx/menu/luma/cursor.tga and b/gfx/menu/luma/cursor.tga differ
index 81e265d9936c94486d5c5145f7305b665113af08..19268ea7d0f2e8d734c66db7c37f789ccb23598f 100644 (file)
Binary files a/gfx/menu/luma/cursor_move.tga and b/gfx/menu/luma/cursor_move.tga differ
index 617323edeece7198d17ece98c8bc5bd738dade12..84f53f99e8543928781fc6941c997152c4cfcc5e 100644 (file)
Binary files a/gfx/menu/luma/cursor_resize.tga and b/gfx/menu/luma/cursor_resize.tga differ
index 606015b012856f35d24d30c4f718e82771c83c40..10a550d512f86d9654a01a28a53c89e825569ef9 100644 (file)
Binary files a/gfx/menu/luma/cursor_resize2.tga and b/gfx/menu/luma/cursor_resize2.tga differ
index 9724f4049782003d45d38640f2db83d01447c717..41e1c2894466410434782d13a60af9e0e0dd4930 100644 (file)
Binary files a/gfx/menu/luma/gametype_as.tga and b/gfx/menu/luma/gametype_as.tga differ
index 196196e5ecd984d8c09e5f87a6036f0a157fc01b..e4e827c7a5a7ebf2b230bee8672796fda38e3428 100644 (file)
Binary files a/gfx/menu/luma/gametype_ca.tga and b/gfx/menu/luma/gametype_ca.tga differ
index eefb2fe0b547d40627a159400a52ec55fbb69b86..13a1175301ea09ec38e4a9687fc7a909a1f63099 100644 (file)
Binary files a/gfx/menu/luma/gametype_ctf.tga and b/gfx/menu/luma/gametype_ctf.tga differ
index da86fa4faf0e27bd33a85a84fc80098bfc534d0f..86c4a1309dc227667d3d7173873c027e398de4ca 100644 (file)
Binary files a/gfx/menu/luma/gametype_cts.tga and b/gfx/menu/luma/gametype_cts.tga differ
index 3d0874ead11edefab9c321165bfa0c802d4a2c12..37a3657b8061eb5191ba8519af9f54f45c8fdfea 100644 (file)
Binary files a/gfx/menu/luma/gametype_dm.tga and b/gfx/menu/luma/gametype_dm.tga differ
index eb6605b5b3db89f84af354e07c7d88538a3986e4..3e7b0dedbd56cc815dc79df3674dba22e292ec2f 100644 (file)
Binary files a/gfx/menu/luma/gametype_dom.tga and b/gfx/menu/luma/gametype_dom.tga differ
index 3558725a542264919c9bf01d0b82b2bcab56b942..ac9a4e2e9658086db86cb1a816cc1f089586db20 100644 (file)
Binary files a/gfx/menu/luma/gametype_duel.tga and b/gfx/menu/luma/gametype_duel.tga differ
index f6228a5106a1fdfa9fe4013e63e63e7c2e629b0f..b0636ba87ab18dd401736369fac19b7837148258 100644 (file)
Binary files a/gfx/menu/luma/gametype_ft.tga and b/gfx/menu/luma/gametype_ft.tga differ
index 90938379725a60b477a751d3ea770965673d2879..237ed6e6f536a0cbdbfebd39d4a7598395e377fa 100644 (file)
Binary files a/gfx/menu/luma/gametype_inf.tga and b/gfx/menu/luma/gametype_inf.tga differ
index d954af1ebc99a0e6c6b94a665f79362f6ac08d02..5003f6ce5a611688c3946da459db945b905ea38a 100644 (file)
Binary files a/gfx/menu/luma/gametype_inv.tga and b/gfx/menu/luma/gametype_inv.tga differ
index e6b35a4e1c40f2ef36b49876ac088901086388db..105ffcfbbc9826bf3993f0ea64c18f6593908ac2 100644 (file)
Binary files a/gfx/menu/luma/gametype_jb.tga and b/gfx/menu/luma/gametype_jb.tga differ
index 38acfc705e989236e7bbe6e259b985cd0017b289..3731ab3c1a59fd23104f268ce4c2c8f0a1f6816c 100644 (file)
Binary files a/gfx/menu/luma/gametype_ka.tga and b/gfx/menu/luma/gametype_ka.tga differ
index df30ff0ea18f5630c3e1f1a3247bc11dcbc39555..a3a678f0f322197b9dda79031a8aec2049f08416 100644 (file)
Binary files a/gfx/menu/luma/gametype_kh.tga and b/gfx/menu/luma/gametype_kh.tga differ
index e14d2a2f9bb58407b2d328c12322cf8ae6e032f5..4cccac846b1389ee18031a4e3518be0cb8e844f0 100644 (file)
Binary files a/gfx/menu/luma/gametype_lms.tga and b/gfx/menu/luma/gametype_lms.tga differ
index 93b021e4555aacef4889d76b5384addc99715edc..37be7c751d113a4053adbd72b4456d82c5f1f098 100644 (file)
Binary files a/gfx/menu/luma/gametype_nb.tga and b/gfx/menu/luma/gametype_nb.tga differ
index 48a0c5a13a37e5ce40de8579fb2f3c0b8d302ecd..d73d19a966f539c2c8e79dbc89cab0fb9c05c523 100644 (file)
Binary files a/gfx/menu/luma/gametype_ons.tga and b/gfx/menu/luma/gametype_ons.tga differ
index a03204238f00dfe439f0b62fc8bbf991a9a4d879..a88c9eef52fb20ebac1903e9af0db58ffb2d8817 100644 (file)
Binary files a/gfx/menu/luma/gametype_rc.tga and b/gfx/menu/luma/gametype_rc.tga differ
index 3a6c0922f4b047a54f7137e430f499745032aedd..2b4a343024924f2dcb514fc0ef2a46c0acade7cd 100644 (file)
Binary files a/gfx/menu/luma/gametype_tdm.tga and b/gfx/menu/luma/gametype_tdm.tga differ
index 8a8f7d984d9ca4e25cae4a32082aa0455557727c..d9af96b9364a9eedba54b94bedd398c506437aad 100644 (file)
Binary files a/gfx/menu/luma/gametype_vip.tga and b/gfx/menu/luma/gametype_vip.tga differ
index c32dd97a85d90dcdc42bf8935af07006b4ee74ca..f7c190e8bc0a563668e52068daa2903e3b5ffb68 100644 (file)
Binary files a/gfx/menu/luma/icon_aeslevel1.tga and b/gfx/menu/luma/icon_aeslevel1.tga differ
index c32dd97a85d90dcdc42bf8935af07006b4ee74ca..f7c190e8bc0a563668e52068daa2903e3b5ffb68 100644 (file)
Binary files a/gfx/menu/luma/icon_aeslevel2.tga and b/gfx/menu/luma/icon_aeslevel2.tga differ
index c90efa8c0d208c899f8cfcb1a6ceb9a332e3c460..f84432ddd328d026ea3c9a476ddbf01f3b1846f0 100644 (file)
Binary files a/gfx/menu/luma/icon_aeslevel3.tga and b/gfx/menu/luma/icon_aeslevel3.tga differ
index c90efa8c0d208c899f8cfcb1a6ceb9a332e3c460..f84432ddd328d026ea3c9a476ddbf01f3b1846f0 100644 (file)
Binary files a/gfx/menu/luma/icon_aeslevel4.tga and b/gfx/menu/luma/icon_aeslevel4.tga differ
index c90efa8c0d208c899f8cfcb1a6ceb9a332e3c460..f84432ddd328d026ea3c9a476ddbf01f3b1846f0 100644 (file)
Binary files a/gfx/menu/luma/icon_aeslevel5.tga and b/gfx/menu/luma/icon_aeslevel5.tga differ
index 83659d08efa00a6c88c137154697f5335ebe2fc5..e7baf78b9cb66cd564e8c16094f169b4bbd7802f 100644 (file)
Binary files a/gfx/menu/luma/icon_ipv4.tga and b/gfx/menu/luma/icon_ipv4.tga differ
index 43d9d41785f364bdb3b61c31641eb93f745adfe0..db474afe755ee253aface8f727fba29488cf7d1b 100644 (file)
Binary files a/gfx/menu/luma/icon_ipv6.tga and b/gfx/menu/luma/icon_ipv6.tga differ
index 79a1f24504e0384a3d224de0113c76b2ae464de1..8ba3747e0ffed110f425687323090bca02742f9a 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_.tga and b/gfx/menu/luma/icon_mod_.tga differ
diff --git a/gfx/menu/luma/icon_mod_XDF.tga b/gfx/menu/luma/icon_mod_XDF.tga
deleted file mode 100644 (file)
index c0b4e6d..0000000
Binary files a/gfx/menu/luma/icon_mod_XDF.tga and /dev/null differ
index ce8c2d50d7704a99a6ce9e36528dc70d9f998479..750bb19b3a672e105e73ec33ed1eb2ea87603c7e 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_instagib.tga and b/gfx/menu/luma/icon_mod_instagib.tga differ
index ba3080a7df31bf0e9e89e9baba6b32807241dcc0..9fc2a9a83aa96b8f7183c54ed19f5af5a27e1d32 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_jeff.tga and b/gfx/menu/luma/icon_mod_jeff.tga differ
index ce8c2d50d7704a99a6ce9e36528dc70d9f998479..750bb19b3a672e105e73ec33ed1eb2ea87603c7e 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_minstagib.tga and b/gfx/menu/luma/icon_mod_minstagib.tga differ
index c5f212c0787e03d5ae2a3d31dfffb0b1827d0bbe..7862e58f389631783449bc6c449897fdd7e22f8d 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_newtoys.tga and b/gfx/menu/luma/icon_mod_newtoys.tga differ
index aeeaeb883a0d02bc86c70416d00e2ec381a38e9b..d72e7f622e46dcfa3935b92e761b49112dd63863 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_overkill.tga and b/gfx/menu/luma/icon_mod_overkill.tga differ
index e9eb0dfd0f14a6aefdc596cddada0f0f95786cee..58901465d5eb0fc89cf3dc5be100cb32d9b3562c 100644 (file)
Binary files a/gfx/menu/luma/icon_mod_quake.tga and b/gfx/menu/luma/icon_mod_quake.tga differ
diff --git a/gfx/menu/luma/icon_mod_xdf.tga b/gfx/menu/luma/icon_mod_xdf.tga
new file mode 100644 (file)
index 0000000..6749a48
Binary files /dev/null and b/gfx/menu/luma/icon_mod_xdf.tga differ
diff --git a/gfx/menu/luma/icon_mod_xpm.tga b/gfx/menu/luma/icon_mod_xpm.tga
new file mode 100644 (file)
index 0000000..47c3fb6
Binary files /dev/null and b/gfx/menu/luma/icon_mod_xpm.tga differ
index fbe4dc9ffacef6a30231ce81766dc638a5aa79e9..041921822fd7ab28e86984c1f735061a6cb4a495 100644 (file)
Binary files a/gfx/menu/luma/icon_pure1.tga and b/gfx/menu/luma/icon_pure1.tga differ
index 841dde650f8ccd05e7ad8303a237635510ec6699..564edf5b3cd366944075edbe57abd72c2ed085c5 100644 (file)
Binary files a/gfx/menu/luma/icon_stats1.tga and b/gfx/menu/luma/icon_stats1.tga differ
index c31aa438efe89a0f3f9653ed4958321c82c95772..658e9c41e531bdaecc4ad896df5481a2a3bb7daa 100644 (file)
Binary files a/gfx/menu/luma/inputbox_f.tga and b/gfx/menu/luma/inputbox_f.tga differ
index 621f50d9cd6ef0536d66f74725681f7bb76afb4f..0bbd45023dbc9b1f478520db1bd870398dc72a76 100644 (file)
Binary files a/gfx/menu/luma/inputbox_n.tga and b/gfx/menu/luma/inputbox_n.tga differ
index 5cd70916d5d2519328d9c0673857aa61148c6803..aa8ee77bd3d22559a3c2d737361eab87b9bd1e99 100644 (file)
Binary files a/gfx/menu/luma/nopreview_map.tga and b/gfx/menu/luma/nopreview_map.tga differ
index c332b6345ac0df9bc63e4cf27fcf7dd1f559ddf7..1806719d0db09944259837da5d13c916659027a6 100644 (file)
Binary files a/gfx/menu/luma/nopreview_menuskin.tga and b/gfx/menu/luma/nopreview_menuskin.tga differ
index e1fa52c67ee88f8c050e2ffe082c31a1e55ce4ec..583a91b47505e0fd3577ff92cba9cef4347e8d24 100644 (file)
Binary files a/gfx/menu/luma/nopreview_player.tga and b/gfx/menu/luma/nopreview_player.tga differ
index 8b60a1b274441481b3254ff9895e166be6d110dc..2cb80f979f2a5e6e438e981479a924e836c9b707 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_c0.tga and b/gfx/menu/luma/radiobutton_c0.tga differ
index d23f9df0207126fee3a5b32274c7c663ad9aed71..d1561539e9ddf8a827b3648c46131476e974d466 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_c1.tga and b/gfx/menu/luma/radiobutton_c1.tga differ
index 16a4ff8a323e0b15d88079964082eb944a67c568..8bc37140dda3bd0bbbaf9f83fa08d198770661ca 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_d0.tga and b/gfx/menu/luma/radiobutton_d0.tga differ
index b4eacffbc29f574eb91b957d4df3ed10c96c090d..93bf16dbd0c9df05ade93d7b5f5e5e9a4bfbc9b1 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_d1.tga and b/gfx/menu/luma/radiobutton_d1.tga differ
index 59ecc0805b5826de64e4a00c7164d9c73c4a2b07..7875e388139b6fdea8d19096a5767f5b1a601785 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_f0.tga and b/gfx/menu/luma/radiobutton_f0.tga differ
index 396e2771a82d3fe4e94714fcada4791209d5ed2f..63ddaf5b47b986f88a3d94e5098a72bbb797ab89 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_f1.tga and b/gfx/menu/luma/radiobutton_f1.tga differ
index 13a96e96900895a580da08f76dae9f5fc351c7fc..150a39286f43fef9f87611f791eecc73e9a13677 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_n0.tga and b/gfx/menu/luma/radiobutton_n0.tga differ
index a64b50cc31b38d79805d86d8c485335de67b61a1..34b6618a93f6b0b8b95c21c1066d5d555e148efe 100644 (file)
Binary files a/gfx/menu/luma/radiobutton_n1.tga and b/gfx/menu/luma/radiobutton_n1.tga differ
index 09333d1412b1d3349d470bced09c77c8bfeeb784..6bedb66c96242d8daa0adce40eb5b559dfbd3759 100644 (file)
Binary files a/gfx/menu/luma/scrollbar_c.tga and b/gfx/menu/luma/scrollbar_c.tga differ
index 109278c9f0997b8a1e1273ca79228f0928646fe2..539328d00af2f1d34cd550511f2ae49f66b5a835 100644 (file)
Binary files a/gfx/menu/luma/scrollbar_f.tga and b/gfx/menu/luma/scrollbar_f.tga differ
index b8a01d1779b3fc878348909232f53d631c8af8d7..e081a306b3d0f98c51c9ead60a5ccc322182816f 100644 (file)
Binary files a/gfx/menu/luma/scrollbar_n.tga and b/gfx/menu/luma/scrollbar_n.tga differ
index 699e11c328f108353d1eb2c79010c68a9633f1f9..c1e21e4f97178ea765b90da1e39cd91f99757a1e 100644 (file)
Binary files a/gfx/menu/luma/scrollbar_s.tga and b/gfx/menu/luma/scrollbar_s.tga differ
index f89a2e605f57526c7d3ec866ca2c08360686c0bd..50e4ffd3dc10ce43c511a0a3e35b53704f8bed45 100644 (file)
Binary files a/gfx/menu/luma/skinpreview.tga and b/gfx/menu/luma/skinpreview.tga differ
index cfaa1e5804da071f55675621780791861d5b3fc2..eef45e9a0a44f8d0439139e9c651b1956c0553cd 100644 (file)
@@ -12,7 +12,7 @@ author sev
 ALIGN_BACKGROUND              c5h5
 ALIGN_BACKGROUND_INGAME       c5h5
 ALPHA_BACKGROUND_INGAME       1
-ALPHA_DISABLED                0.2
+ALPHA_DISABLED                0.25
 ALPHA_BEHIND                  0.5
 
 // button
@@ -115,7 +115,7 @@ POSITION_DIALOG_CREDITS       '-0.05 1.2 0'
 POSITION_DIALOG_QUIT          '1.05 1.2 0'
 
 // font
-ALPHA_TEXT                    0.8
+ALPHA_TEXT                    0.875
 COLOR_TEXT                    '0.96 0.99 1'
 ALPHA_HEADER                  0.5
 COLOR_HEADER                  '0.96 0.99 1'
@@ -144,7 +144,7 @@ COLOR_KEYGRABBER_TITLES       '0.03 0.25 0.49'
 ALPHA_LISTBOX_BACKGROUND      0.5
 COLOR_LISTBOX_BACKGROUND      '0 0 0'
 ALPHA_LISTBOX_SELECTED        1
-COLOR_LISTBOX_SELECTED        '0.97 0.56 0.27'
+COLOR_LISTBOX_SELECTED        '0.9 0.53 0.28'
 ALPHA_LISTBOX_WAITING         0.8
 COLOR_LISTBOX_WAITING         '0.73 0.82 0.9'
 ALPHA_LISTBOX_FOCUSED         0.55
@@ -209,7 +209,7 @@ WIDTH_SLIDERTEXT              0.333333333333
 
 // tooltip
 ALPHA_TOOLTIP                 0.8
-COLOR_TOOLTIP                 '1 0.97 0.94'
+COLOR_TOOLTIP                 '1 0.97 0.95'
 AVOID_TOOLTIP                 '8 8 0'
 BORDER_TOOLTIP                '16 16 0'
 MARGIN_TOOLTIP                '10 8 0'
index 8471b32d97116be45571b4cae98e72c3867b8f2f..b460e27408a59537974f89c84c4e6c4595f8fd57 100644 (file)
Binary files a/gfx/menu/luma/slider_c.tga and b/gfx/menu/luma/slider_c.tga differ
index c264fee985d36a9cdb5ba83df90e8b46b8fc1be5..fbd76c22a51feafb9b9b7a75c7d71057959c29d8 100644 (file)
Binary files a/gfx/menu/luma/slider_d.tga and b/gfx/menu/luma/slider_d.tga differ
index 6ff26873fd55e53d8fa899d659b23569235a4688..96e5318df2650c17651f394aaab6c61419f0452e 100644 (file)
Binary files a/gfx/menu/luma/slider_f.tga and b/gfx/menu/luma/slider_f.tga differ
index 2ee36af322ef7227cfe5f48025a747d73378da03..4d68120e6ef127ef16530f2299a0b5a9b8b92f8d 100644 (file)
Binary files a/gfx/menu/luma/slider_n.tga and b/gfx/menu/luma/slider_n.tga differ
index e5b7141dd3203c1451432a5f14f1228904025499..e78e8a413439a5c2ea61592a0ca9d51c2ef75293 100644 (file)
Binary files a/gfx/menu/luma/slider_s.tga and b/gfx/menu/luma/slider_s.tga differ
index 32ea27c8ed72174d0764a25a6f81d698b2e244e2..fbb29936a640fdf07ed9a50b8c6ac73904931b51 100644 (file)
Binary files a/gfx/menu/luma/tooltip.tga and b/gfx/menu/luma/tooltip.tga differ
diff --git a/gfx/menu/luminos/icon_mod_xpm.tga b/gfx/menu/luminos/icon_mod_xpm.tga
new file mode 100644 (file)
index 0000000..3d51ff3
Binary files /dev/null and b/gfx/menu/luminos/icon_mod_xpm.tga differ
diff --git a/gfx/menu/wickedx/icon_mod_xpm.tga b/gfx/menu/wickedx/icon_mod_xpm.tga
new file mode 100644 (file)
index 0000000..3d51ff3
Binary files /dev/null and b/gfx/menu/wickedx/icon_mod_xpm.tga differ
diff --git a/gfx/menu/xaw/icon_mod_xpm.tga b/gfx/menu/xaw/icon_mod_xpm.tga
new file mode 100644 (file)
index 0000000..3d51ff3
Binary files /dev/null and b/gfx/menu/xaw/icon_mod_xpm.tga differ
index 71cbd68a71540d7a6800a6fb18bad869d273c58a..66bfa6e308efa09a4a5d7d7bc0fea047e6f1dceb 100644 (file)
Binary files a/gfx/scoreboard/player_ready.tga and b/gfx/scoreboard/player_ready.tga differ
index 443abc4f6de2870396896400af6a19d4062eb1fd..c67a4ace5f91e65d9bace365648c3c17af452552 100644 (file)
Binary files a/gfx/scoreboard/playercolor_base.tga and b/gfx/scoreboard/playercolor_base.tga differ
index f9c7307973ec1adfacc097a5608c24311d114c92..d48ea9a98c042983a579e830fe2d0cd30383016b 100644 (file)
Binary files a/gfx/scoreboard/playercolor_pants.tga and b/gfx/scoreboard/playercolor_pants.tga differ
index eeeaf4c370a8442f7e296117437375303a84e4ba..54a3826006049db3400693946e763b250599094a 100644 (file)
Binary files a/gfx/scoreboard/playercolor_shirt.tga and b/gfx/scoreboard/playercolor_shirt.tga differ
index e8a5d348b3a7a443b0094f50c2aef305e8973637..13bc9f3fb63a538a8fecd24ecd0bebee34869311 100644 (file)
Binary files a/gfx/scoreboard/scoreboard_bg.tga and b/gfx/scoreboard/scoreboard_bg.tga differ
index 122d4f482d56e3fd506617579c5e06e739cdb1f0..9a050d5ca646b469ea1d893a674d0d6649dfc55b 100644 (file)
Binary files a/gfx/scoreboard/scoreboard_tableheader.tga and b/gfx/scoreboard/scoreboard_tableheader.tga differ
diff --git a/help-overkill.cfg b/help-overkill.cfg
new file mode 100644 (file)
index 0000000..e48147c
--- /dev/null
@@ -0,0 +1,18 @@
+set help_msg_0 "Wondering why you die so often? Because you're ignoring armor and health pickups"
+set help_msg_1 "Use secondary fire (blaster) on the floor to jump higher"
+set help_msg_2 "Double press W after spawning to accelerate by jumping forward (=dodging)"
+set help_msg_3 "Keep the jump key pressed to stay fast (=bunny-hopping)"
+set help_msg_4 "Run along a flat wall and quickly press W twice to gain speed (=wall dodging)"
+set help_msg_5 "Pick up armor shards from dead enemies to survive the next hit"
+set help_msg_6 "Stand *completely* still, then quickly double press W, A, S or D to dodge"
+set help_msg_7 "Use the blaster to make large jumps or climb walls"
+set help_msg_8 "Use the Shotgun (or Machine Gun) to slow down fast players"
+set help_msg_9 "When running, blaster the floor or walls to gain more speed"
+set help_msg_10 "Use G (default key) to throw Nades (you want the 'dropweapon' bind, not 'hook', for better timing)"
+set help_msg_11 "Don't reload, switch to Shotgun"
+set help_msg_12 "You can blaster the flag or dropped Nades to push them away"
+set help_msg_13 "This is how most pros throw Nades: press G (dropweapon bind), wait *several* seconds, press G again"
+set help_msg_14 "Dodge forward to climb walls faster"
+set help_msg_15 "Double press W, then hold space to start moving"
+set help_msg_16 "Spectate stronger players to learn their tricks"
+set help_msg_count 17 // update this when adding messages - it should be the number of messages (which means last message index + 1)
diff --git a/help-xonotic.cfg b/help-xonotic.cfg
new file mode 100644 (file)
index 0000000..1bd9e27
--- /dev/null
@@ -0,0 +1,60 @@
+set help_msg_0 "Big Admin is watching you, so please be friendly or feel their almighty ban-hammer!"
+set help_msg_1 "If you want to learn more about Xonotic, read ^1'Halogene's Newbie Corner' (https://xonotic.org/guide) ${help_cfg_prefix}as it contains lots of useful tips and tricks, explains all the weapons and helps to improve your gameplay."
+set help_msg_2 "Please watch out for balanced teams and change by pressing F5 (teammenu) or F6 (auto join 'best' team)."
+set help_msg_3 "When trying to bunny-hop you can ^1hold the jump button ${help_cfg_prefix}while you are still in the air, this will make those jumps VERY easy to time and work more reliable."
+set help_msg_4 "When a vote is called you can accept it via F1 or reject it via F2 (default keys)."
+set help_msg_5 "Spectating other (good) players helps to learn new tricks. To spectate press F3 and then Mouse1 to switch between the players you want to spectate. F5, F6 or jump will get you back into the game (default keys)."
+set help_msg_6 "Being a beginner is great! You can learn so many new tricks and improve quickly. Watch others, ask for advice and use your common sense effectively."
+set help_msg_7 "If others are better than you, it does not mean they cheat. Save such complaints for when you have more experience and know what kind of funky stuff is possible."
+set help_msg_8 "In CTF, it's good to move around and get involved in the action. You get fragged quite a bit but you are also most helpful to your team."
+set help_msg_9 "Use the radar to see where your teammates are. Pressing zoom will expand the radar image to give you a better overview."
+set help_msg_10 "Most teammessages display waypoints by default. Use those to guide your teammates. You can also see them and the flagcarrier in the radar."
+set help_msg_11 "Protect your flagcarrier at all cost! Also save health and armor for him, he might need them more than you!"
+set help_msg_12 "You can use the Blaster and most explosive weapons to jump around. Just look 'at your feet' and press fire. If you also jump at the same time, you get even higher."
+set help_msg_13 "Be friendly and helpful to other players! Being angry at others' mistakes is understandable, but nobody is perfect. Try to use calm words when telling them how to correct their mistake."
+set help_msg_14 "You can use the zoom key with all guns, only the Vortex has it as a extra function on Mouse2 (default key)."
+set help_msg_15 "Notice what is happening around you! If your base is empty in CTF, then STAY and defend the flag! Make sure someone defends the flagcarrier or assist him yourself."
+set help_msg_16 "You can drop the weapon you currently have with Backspace (default key). You can help your unarmed teammates this way."
+set help_msg_17 "Learn to use the team messages! You find them in the Setting/Input menu. Try changing them to keys you can press easier than the defaults."
+set help_msg_18 "Gaming should be fun! Try to have a nice time, be helpful, mindful and treat others like you want to be treated."
+set help_msg_19 "Visit the official forum on ^1https://forums.xonotic.org/ ${help_cfg_prefix}and feel free to open a thread if you have questions."
+set help_msg_20 "If you already have a good weapon, it's a great idea to let your teammates get something better than the Shotgun too!"
+set help_msg_21 "Press T to chat with others, press Y for messages to your team only, press TAB to see the scores and U for the chat history (default keys)."
+set help_msg_22 "You can use ^1'suggestmap PART_OF_NAME' ${help_cfg_prefix}to make a map come up at the vote screen after a map was played."
+set help_msg_23 "The console is accessible through the ~ key or by pressing Shift+ESC. It has many more advanced features, use 'cmdlist' and 'cvarlist' to get a full list of available commands/settings."
+set help_msg_24 "The Blaster is a useful tool for gaining speed and jumping around, but it does little damage."
+set help_msg_25 "The Shotgun's primary firemode has spread and is more useful at a closer distance, use secondary for the melee attack and slap into other players faces!"
+set help_msg_26 "The Machine Gun secondary has a burst fire mode and less spread than the primary mode."
+set help_msg_27 "The Mortar is a good all around gun but takes some practice to aim it, because of the projectile's curve."
+set help_msg_28 "The Electro has a combo attack. Fire the primary mode at the balls from the secondary mode for a huge and powerful explosion."
+set help_msg_29 "The Crylink's primary fire bounces. Both firemodes pull your enemies, making it a great tool to stop a flag carrier."
+set help_msg_30 "The Vortex is a powerful sniper gun. Aim carefully!"
+set help_msg_31 "The Hagar is underestimated, but very powerful if you aim right. The secondary mode charges up to four rockets and causes devasting damage if you release them."
+set help_msg_32 "The Devastator is powerful but slow. Keep Mouse1 pressed to guide the rockets. Secondary firemode makes the rocket(s) explode."
+set help_msg_33 "The Arc is a strong lighting beam, which bends slighty if you move your mouse. The secondary firemode is very strong but short. Both firemodes also heal teammates if you shoot at them."
+set help_msg_34 "By default, explosions go through walls. Be careful when hiding behind walls!"
+set help_msg_35 "Get on IRC to chat with fellow players. Take a look at ^1https://xonotic.org/chat/${help_cfg_prefix}."
+set help_msg_36 "Don't drink and frag."
+set help_msg_37 "Don't shoot at players who are typing/chatting. You recognize those players by the keyboard symbols above their head."
+set help_msg_38 "'gg' is shorthand for 'Good Game', 'gl' means 'Good luck' and 'hf' 'Have fun'."
+set help_msg_39 "Players with the prefix '$bot_prefix${help_cfg_prefix}' in their nick are bots on this server. There is also a clan named [BOT]."
+set help_msg_40 "You spawn with ^1two ${help_cfg_prefix}weapons. Use the Blaster for much faster movement."
+set help_msg_41 "Visit the stats page at ^1https://stats.xonotic.org/ ${help_cfg_prefix}and check out how you are doing in the rankings!"
+set help_msg_42 "Start playing 1on1 if you want to learn fast"
+set help_msg_43 "Visit ^1https://xonotic.org/pickup/ ${help_cfg_prefix}to get in touch with the experienced players, ask them stuff and play with them!"
+set help_msg_44 "Look for servers that have a good ping for you. You can't play this game well with a ping > 100. If there are no servers close enough to you, cooperate with your friends to setup one."
+set help_msg_45 "If you want to play the next map you can cast a vote via 'vcall endmatch' in the console. Other players can vote using F1 and F2 (default keys)."
+set help_msg_46 "Always watch your back. Do not just run away, fight back as you retreat. Otherwise, you could be shot in the back."
+set help_msg_47 "Try to get as much armor and health as you can, but remember your teammates need armor and health too."
+set help_msg_48 "Do not attack if you have neither a good weapon, nor health, nor armor."
+set help_msg_49 "Standing still makes you an easy target. You can move around the map faster by bunny hopping."
+set help_msg_50 "You can use the Blaster to climb up walls. Before trying this, become familiar with the basics of Blaster movement."
+set help_msg_51 "You can control your movement in air. Use it to prevent yourself from falling off the map when somebody starts pushing you around."
+set help_msg_52 "Use the Blaster, Mortar or Devastator in space maps to push other players off the map. They will enjoy it."
+set help_msg_53 "You can turn off automatic weapon changing in the Player menu. If you configure your key bindings, manually switching weapons can be faster and easier."
+set help_msg_54 "Choose the right weapon for the job, not just the one that the game automatically puts in your hand."
+set help_msg_55 "Enter ^1'lsmaps' ${help_cfg_prefix}in the console to get a list of maps configured on the server."
+set help_msg_56 "While you are in the air, release the forward key, press one of the left/right keys and move your mouse in the same direction. This will bend your fly/jump path."
+set help_msg_57 "Hold the jump key to keep on jumping (called ^1'bunny-hopping'${help_cfg_prefix}) which will accelerate your speed."
+set help_msg_58 "'xonotic.org/guide changed my life' -- ^x777S^x090Є^x088Є^x000Қ^x900⁻ʸ${help_cfg_prefix}, 2018"
+set help_msg_count 59 // update this when adding messages - it should be the number of messages (which means last message index + 1)
diff --git a/help.cfg b/help.cfg
new file mode 100644 (file)
index 0000000..52cfb09
--- /dev/null
+++ b/help.cfg
@@ -0,0 +1,24 @@
+// Simple help message system
+// It prints messages with the configured name
+
+// You can start the help system with the command help_loop but this has been found to annoy players.
+// A better way is to put `alias sv_hook_gamestart_all "defer 20 help_next"` into your server.cfg,
+// that way you get one message per match.
+
+// the messages need to be starting from 0 and be consecutive
+// for manual use: help_inc switches to the next message, help_doit will print the current message, help_next will do both together
+
+// settings
+set help_cfg_nick "^2Help System^3" "the messages will appear in chat coming from the sever using this name"
+set help_cfg_time 5 "the time between two messages in seconds when started using help_loop"
+set help_cfg_prefix "^2" "prepended to each message, useful to color the nick and message differently"
+
+// aliases making up the actual helpsystem
+set help_tmp_index -1 // -1 since we first increment, then show it
+alias help_say "set help_tmp_oldnick \"$sv_adminnick\"; set sv_adminnick \"$help_cfg_nick\"; say \"$*\"; help_say2"
+alias help_say2 "set sv_adminnick \"$help_tmp_oldnick\""
+alias help_doit "sv_cmd rpn /help_tmp_msg help_msg_$help_tmp_index def; help_doit2"
+alias help_doit2 "help_say $help_cfg_prefix$help_tmp_msg"
+alias help_inc "sv_cmd rpn /help_tmp_index help_tmp_index 1 add $help_msg_count mod def"
+alias help_next "help_inc; help_doit" // increment first - if the ruleset changed, the number of tips could have too, this avoids overflow
+alias help_loop "help_next; defer $help_cfg_time help_loop"
index e16e0b878637b50dd21f71c361f88866b5c54379..234bedc596fd231247e211ac2dacf3e61cf2cfda 100644 (file)
@@ -7,7 +7,7 @@ es    "Spanish" "Español" 99%
 fr    "French" "Français" 99%
 ga    "Irish" "Irish" 35%
 it    "Italian" "Italiano" 99%
-hu    "Hungarian" "Magyar" 55%
+hu    "Hungarian" "Magyar" 57%
 nl    "Dutch" "Nederlands" 70%
 pl    "Polish" "Polski" 81%
 pt    "Portuguese" "Português" 98%
@@ -20,6 +20,6 @@ bg    "Bulgarian" "Български" 68%
 ru    "Russian" "Русский" 99%
 sr    "Serbian" "Српски" 71%
 uk    "Ukrainian" "Українська" 57%
-zh_CN "Chinese (China)" "中文" 62%
+zh_CN "Chinese (China)" "中文" 63%
 zh_TW "Chinese (Taiwan)" "國語" 68%
 ko    "Korean" "한국의" 33%
index 04f6afed4c74eb3e65fdd54bb905bef383797a3f..792d14536bc919802b0c538f23d2cd9915c1fee6 100644 (file)
Binary files a/models/items/a_cells.md3 and b/models/items/a_cells.md3 differ
index b325c9c2be28a5cdbafcee989336d78cd0c35900..be0ea1770e397ffeea0050ce07a0726a047432a4 100644 (file)
Binary files a/models/weapons/g_crylink.md3 and b/models/weapons/g_crylink.md3 differ
index 76c2e8962be3a6e84882eb8f9b9d7c299ddce2bb..022891a97e4ce6ac5724f51eb3392ed490b47993 100644 (file)
Binary files a/models/weapons/g_electro.md3 and b/models/weapons/g_electro.md3 differ
index d8de4644f5e64c78810c72f4d599de65e8645fd1..62d571bd2560b876ad083a6d779ee96991df3a92 100644 (file)
Binary files a/models/weapons/h_crylink.iqm and b/models/weapons/h_crylink.iqm differ
index 0a59625b6a6aa297525c008a66ba879537a5ac08..92a05ebf36fa680acd628cdc34e76e84f3dfb599 100644 (file)
@@ -1,4 +1,9 @@
-1 8 20 0 // fire
-9 5 20 0 // fire2
-15 200 20 1 // idle
-215 40 20 0 // reload
+/*
+Generated framegroups file for h_crylink
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 26 30 0 // h_crylink fire
+27 26 30 0 // h_crylink fire
+53 101 3 1 // h_crylink idle
+154 101 3 1 // h_crylink idle
index a726b33f2408f1f10586710183ba2c3eef2b0a7a..df0d79506daea7196c2f46c0648f34ccfaba1268 100644 (file)
Binary files a/models/weapons/h_electro.iqm and b/models/weapons/h_electro.iqm differ
index 0a59625b6a6aa297525c008a66ba879537a5ac08..f6df25a7171aa3bf97f591107f06442f7ee2cfe2 100644 (file)
@@ -1,4 +1,9 @@
-1 8 20 0 // fire
-9 5 20 0 // fire2
-15 200 20 1 // idle
-215 40 20 0 // reload
+/*
+Generated framegroups file for h_electro
+Used by DarkPlaces to simulate frame groups in DPM models.
+*/
+
+1 36 30 0 // h_electro fire
+37 36 30 0 // h_electro fire
+73 101 3 1 // h_electro idle
+174 101 3 1 // h_electro idle
index c3b9f3ca833a84a8818bf32df9b6a914fc8a843e..dc73d47dc09c65dc204b38f329a80fc5c1bd9f3e 100644 (file)
Binary files a/models/weapons/v_crylink.md3 and b/models/weapons/v_crylink.md3 differ
index 67105bcf6f1686260de4501e9d485f8a1e69b112..2e45a678665fe15200078d3533e2b1bf6d8783b2 100644 (file)
Binary files a/models/weapons/v_electro.md3 and b/models/weapons/v_electro.md3 differ
index feb9b871b0d9170ac4356bcec4480fc3212f3bf7..07423e3aac74c1034b1587c1b69febcd78eae0d3 100644 (file)
@@ -235,7 +235,6 @@ seta notification_INFO_ITEM_WEAPON_PRIMORSEC "0" "0 = off, 1 = print to console,
 seta notification_INFO_ITEM_WEAPON_UNAVAILABLE "0" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_JETPACK_NOFUEL "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_JOIN_CONNECT "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_JOIN_CONNECT_TEAM "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_JOIN_PLAY "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_JOIN_PLAY_TEAM "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_KEEPAWAY_DROPPED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -262,7 +261,7 @@ seta notification_INFO_POWERUP_STRENGTH "1" "0 = off, 1 = print to console, 2 =
 seta notification_INFO_QUIT_DISCONNECT "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_QUIT_KICK_IDLING "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_QUIT_KICK_SPECTATING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_QUIT_SPECTATE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_QUIT_SPECTATE "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_RACE_ABANDONED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_RACE_FAIL_RANKED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_RACE_FAIL_UNRANKED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -282,7 +281,7 @@ seta notification_INFO_SUPERSPEC_MISSING_UID "2" "0 = off, 1 = print to console,
 seta notification_INFO_SUPERWEAPON_PICKUP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_LARGERTEAM "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_NOTALLOWED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_VERSION_BETA "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_VERSION_BETA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_OLD "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_OUTDATED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_WATERMARK "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -694,11 +693,11 @@ seta notification_WEAPON_VAPORIZER_MURDER "1" "Enable this multiple notification
 seta notification_WEAPON_VORTEX_MURDER "1" "Enable this multiple notification"
 
 // MSG_CHOICE notifications (count = 28):
-seta notification_CHOICE_CTF_CAPTURE_BROKEN "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
+seta notification_CHOICE_CTF_CAPTURE_BROKEN "2" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
 seta notification_CHOICE_CTF_CAPTURE_BROKEN_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
-seta notification_CHOICE_CTF_CAPTURE_TIME "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
+seta notification_CHOICE_CTF_CAPTURE_TIME "2" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
 seta notification_CHOICE_CTF_CAPTURE_TIME_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
-seta notification_CHOICE_CTF_CAPTURE_UNBROKEN "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
+seta notification_CHOICE_CTF_CAPTURE_UNBROKEN "2" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
 seta notification_CHOICE_CTF_CAPTURE_UNBROKEN_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
 seta notification_CHOICE_CTF_PICKUP_ENEMY "1" "Choice for this notification 0 = off, 1 = default message, 2 = verbose message"
 seta notification_CHOICE_CTF_PICKUP_ENEMY_ALLOWED "2" "Allow choice for this notification 0 = off, 1 = only in warmup mode, 2 = always"
index 8eb3ca1dcb1a623111fc2a7a095d31ff946ebdc0..db756608defe4a18404579bd22bca0c42b97625c 100644 (file)
@@ -39,13 +39,13 @@ float autocvar_cl_effects_lightningarc_drift_end;
 float autocvar_cl_effects_lightningarc_drift_start;
 float autocvar_cl_effects_lightningarc_segmentlength;
 bool autocvar_cl_effects_lightningarc_simple;
-int autocvar_cl_gentle;
+bool autocvar_cl_gentle;
 int autocvar_cl_gentle_damage;
 int autocvar_cl_gentle_gibs;
 int autocvar_cl_gentle_messages;
 float autocvar_cl_gibs_damageforcescale = 3.5;
 float autocvar_cl_gibs_lifetime = 14;
-float autocvar_cl_gibs_maxcount = 100;
+int autocvar_cl_gibs_maxcount = 100;
 bool autocvar_cl_gibs_sloppy = 1;
 float autocvar_cl_gibs_ticrate = 0.1;
 float autocvar_cl_gibs_velocity_random = 1;
@@ -395,12 +395,12 @@ string autocvar_hud_skin;
 float autocvar_menu_mouse_speed;
 string autocvar_menu_skin;
 int autocvar_r_fakelight;
-int autocvar_r_fullbright;
+bool autocvar_r_fullbright;
 float autocvar_r_letterbox;
 string autocvar_scoreboard_columns;
 bool autocvar_v_flipped;
-float autocvar_vid_conheight;
-float autocvar_vid_conwidth;
+int autocvar_vid_conheight;
+int autocvar_vid_conwidth;
 float autocvar_vid_pixelheight;
 float autocvar_viewsize;
 bool autocvar_cl_eventchase_vehicle = 1;
@@ -411,6 +411,8 @@ float autocvar_cl_hitsound_min_pitch = 0.75;
 float autocvar_cl_hitsound_max_pitch = 1.5;
 float autocvar_cl_hitsound_nom_damage = 25;
 float autocvar_cl_hitsound_antispam_time;
+int autocvar_cl_eventchase_spectated_change = 1;
+float autocvar_cl_eventchase_spectated_change_time = 1;
 int autocvar_cl_eventchase_death = 1;
 float autocvar_cl_eventchase_distance = 140;
 bool autocvar_cl_eventchase_frozen = false;
index 035ba2a1647c29a2445ca54d73022670536cfed3..522859c8b193ea1d4c4729b72ba50d6322aafc5c 100644 (file)
@@ -600,7 +600,7 @@ void CSQCModel_Hook_PreDraw(entity this, bool isplayer)
                return;
        this.csqcmodel_predraw_run = framecount;
 
-       if(!this.modelindex || this.model == "null")
+       if(!this.modelindex || this.model == "null" || this.alpha < 0)
        {
                this.drawmask = 0;
                return;
@@ -608,7 +608,7 @@ void CSQCModel_Hook_PreDraw(entity this, bool isplayer)
        else
                this.drawmask = MASK_NORMAL;
 
-       if(this.isplayermodel) // this checks if it's a player MODEL!
+       if(this.isplayermodel && this.drawmask) // this checks if it's a player MODEL!
        {
                CSQCPlayer_ModelAppearance_Apply(this, this.entnum == player_localnum + 1);
                CSQCPlayer_LOD_Apply(this);
index 9a5335eff06b7c4ca56a73b17d3324b2a2b9618e..5204e8f36dbcd09f54912bab92b453094914e01c 100644 (file)
@@ -5,7 +5,7 @@
 float          scoreboard_showscores;
 float          scoreboard_showaccuracy;
 .string                message;
-.int           renderflags;
+.float         renderflags;
 // float               coop;
 // float               deathmatch;
 
@@ -16,7 +16,6 @@ float         dmg_take;
 // Darkplaces Render Modifications
 #if 0
 .float alpha;
-.float renderflags;
 .vector colormod;
 .float scale;
 #endif
@@ -78,6 +77,7 @@ float nb_pb_period;
 // 0 - playing
 // >0 - id of spectated player
 float spectatee_status;
+float spectatee_status_changed_time;
 
 // short mapname
 string shortmapname;
index 2becced8e0cbcda94ad910946fc4480939b21856..bee8d0568ec2ac1afe7b7ba2704cb3dc77b08f9f 100644 (file)
@@ -561,6 +561,26 @@ void Hud_Dynamic_Frame()
        HUD_Scale_Disable();
 }
 
+bool HUD_WouldShowCursor()
+{
+       if(autocvar__hud_configure)
+               return true;
+       if(hud_panel_radar_mouse)
+               return true;
+       if(mv_active)
+               return true;
+       //entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1)); // TODO: doesn't use regular cursor handling
+       //if(local_player.viewloc && (local_player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+               //return true;
+       if(HUD_Radar_Clickable())
+               return true;
+       if(HUD_MinigameMenu_IsOpened())
+               return true;
+       if(QuickMenu_IsOpened())
+               return true;
+       return false;
+}
+
 void HUD_Main()
 {
        int i;
@@ -676,10 +696,15 @@ void HUD_Main()
                HUD_Panel_Draw(HUD_PANEL(RADAR));
        if(autocvar__con_chat_maximized)
                HUD_Panel_Draw(HUD_PANEL(CHAT));
-       if(hud_panel_quickmenu)
+       if (QuickMenu_IsOpened())
                HUD_Panel_Draw(HUD_PANEL(QUICKMENU));
        HUD_Panel_Draw(HUD_PANEL(SCOREBOARD));
 
+       bool cursor_active_prev = cursor_active;
+       cursor_active = HUD_WouldShowCursor();
+       if (cursor_active_prev != cursor_active && autocvar_hud_cursormode)
+               setcursormode(cursor_active);
+
        if (intermission == 2)
                HUD_Reset();
 
index 68d1e6bffabb1c327a75d5068b91a7cfd072683e..d2349a6a5c60bef7f384b1c31d41cb89114c88e6 100644 (file)
@@ -7,6 +7,8 @@ void Hud_Dynamic_Frame();
 
 bool HUD_Radar_Clickable();
 void HUD_Radar_Mouse();
+bool HUD_WouldShowCursor();
+bool QuickMenu_IsOpened();
 
 REGISTRY(hud_panels, BITS(6))
 #define hud_panels_from(i) _hud_panels_from(i, NULL)
@@ -73,8 +75,6 @@ int vote_prev; // previous state of vote_active to check for a change
 float vote_alpha;
 float vote_change; // "time" when vote_active changed
 
-float hud_panel_quickmenu;
-
 vector mousepos;
 vector panel_click_distance; // mouse cursor distance from the top left corner of the panel (saved only upon a click)
 vector panel_click_resizeorigin; // coordinates for opposite point when resizing
@@ -228,7 +228,7 @@ REGISTER_HUD_PANEL(MINIGAMEHELP,    HUD_MinigameHelp,   PANEL_CONFIG_NO
 REGISTER_HUD_PANEL(MINIGAMEMENU,    HUD_MinigameMenu,   PANEL_CONFIG_NO                          , PANEL_SHOW_MAINGAME | PANEL_SHOW_MINIGAME | PANEL_SHOW_MAPVOTE | PANEL_SHOW_WITH_SB) // MINIGAMEMENU
 REGISTER_HUD_PANEL(MAPVOTE,         MapVote_Draw,       PANEL_CONFIG_NO                          ,                                             PANEL_SHOW_MAPVOTE                     ) // MAPVOTE
 REGISTER_HUD_PANEL(ITEMSTIME,       HUD_ItemsTime,      PANEL_CONFIG_MAIN | PANEL_CONFIG_CANBEOFF, PANEL_SHOW_MAINGAME                                                                ) // ITEMSTIME
-REGISTER_HUD_PANEL(QUICKMENU,       HUD_QuickMenu,      PANEL_CONFIG_MAIN                        , PANEL_SHOW_MAINGAME                                                                ) // QUICKMENU
+REGISTER_HUD_PANEL(QUICKMENU,       HUD_QuickMenu,      PANEL_CONFIG_MAIN                        , PANEL_SHOW_MAINGAME | PANEL_SHOW_MINIGAME                                          ) // QUICKMENU
 REGISTER_HUD_PANEL(SCOREBOARD,      Scoreboard_Draw,    PANEL_CONFIG_NO                          , PANEL_SHOW_MAINGAME | PANEL_SHOW_MINIGAME | PANEL_SHOW_MAPVOTE | PANEL_SHOW_WITH_SB) // SCOREBOARD
 // always add new panels to the end of list
 
index e8ec807be5bc4f9c4e0f59e9410b748da1520757..3043e6e6860d90a0673829688f681eff6fb44e0d 100644 (file)
@@ -5,6 +5,7 @@
 #include <client/autocvars.qh>
 #include <client/defs.qh>
 #include <client/miscfunctions.qh>
+#include <client/view.qh>
 
 #define HUD_Write(s) fputs(fh, s)
 #define HUD_Write_Cvar(cvar) HUD_Write(strcat("seta ", cvar, " \"", cvar_string(cvar), "\"\n"))
@@ -253,6 +254,7 @@ void HUD_Configure_Exit_Force()
                hud_configure_menu_open = 0;
                localcmd("togglemenu\n");
        }
+       cursor_type = CURSOR_NORMAL;
        cvar_set("_hud_configure", "0");
 }
 
@@ -760,7 +762,7 @@ float HUD_Panel_InputEvent(float bInputType, float nPrimary, float nSecondary)
                if (bInputType == 1)
                        return true;
                if (!hud_configure_menu_open)
-                       cvar_set("_hud_configure", "0");
+                       HUD_Configure_Exit_Force();
        }
        else if(nPrimary == K_TAB && hudShiftState & S_CTRL) // switch panel
        {
@@ -951,7 +953,7 @@ LABEL(find_tab_panel)
        return true;
 }
 
-float HUD_Panel_Check_Mouse_Pos(float allow_move)
+int HUD_Panel_Check_Mouse_Pos(bool allow_move)
 {
        int i, j = 0;
        while(j < hud_panels_COUNT)
@@ -968,30 +970,30 @@ float HUD_Panel_Check_Mouse_Pos(float allow_move)
                // move
                if(allow_move && mousepos.x > panel_pos.x && mousepos.y > panel_pos.y && mousepos.x < panel_pos.x + panel_size.x && mousepos.y < panel_pos.y + panel_size.y)
                {
-                       return 1;
+                       return CURSOR_MOVE;
                }
                // resize from topleft border
                else if(mousepos.x >= panel_pos.x - border && mousepos.y >= panel_pos.y - border && mousepos.x <= panel_pos.x + 0.5 * panel_size.x && mousepos.y <= panel_pos.y + 0.5 * panel_size.y)
                {
-                       return 2;
+                       return CURSOR_RESIZE;
                }
                // resize from topright border
                else if(mousepos.x >= panel_pos.x + 0.5 * panel_size.x && mousepos.y >= panel_pos.y - border && mousepos.x <= panel_pos.x + panel_size.x + border && mousepos.y <= panel_pos.y + 0.5 * panel_size.y)
                {
-                       return 3;
+                       return CURSOR_RESIZE2;
                }
                // resize from bottomleft border
                else if(mousepos.x >= panel_pos.x - border && mousepos.y >= panel_pos.y + 0.5 * panel_size.y && mousepos.x <= panel_pos.x + 0.5 * panel_size.x && mousepos.y <= panel_pos.y + panel_size.y + border)
                {
-                       return 3;
+                       return CURSOR_RESIZE2;
                }
                // resize from bottomright border
                else if(mousepos.x >= panel_pos.x + 0.5 * panel_size.x && mousepos.y >= panel_pos.y + 0.5 * panel_size.y && mousepos.x <= panel_pos.x + panel_size.x + border && mousepos.y <= panel_pos.y + panel_size.y + border)
                {
-                       return 2;
+                       return CURSOR_RESIZE;
                }
        }
-       return 0;
+       return CURSOR_NORMAL;
 }
 
 // move a panel to the beginning of the panel order array (which means it gets drawn last, on top of everything else)
@@ -1111,15 +1113,11 @@ void HUD_Panel_EnableMenu()
        hud_configure_menu_open = 2;
        localcmd("menu_showhudoptions ", highlightedPanel.panel_name, "\n");
 }
-float mouse_over_panel;
 void HUD_Panel_Mouse()
 {
        if(autocvar__menu_alpha == 1)
                return;
 
-       if (!autocvar_hud_cursormode)
-               update_mousepos();
-
        if(mouseClicked)
        {
                if(prevMouseClicked == 0)
@@ -1150,7 +1148,7 @@ void HUD_Panel_Mouse()
                                        prevMouseClickedTime = time;
                                        prevMouseClickedPos = mousepos;
                                }
-                               mouse_over_panel = HUD_Panel_Check_Mouse_Pos(mouseClicked & S_MOUSE1);
+                               cursor_type = HUD_Panel_Check_Mouse_Pos(mouseClicked & S_MOUSE1);
                        }
                }
                else
@@ -1203,25 +1201,12 @@ void HUD_Panel_Mouse()
                if(prevMouseClicked)
                        highlightedAction = 0;
                if(hud_configure_menu_open == 2)
-                       mouse_over_panel = 0;
+                       cursor_type = CURSOR_NORMAL;
                else
-                       mouse_over_panel = HUD_Panel_Check_Mouse_Pos(true);
-               if (mouse_over_panel && !tab_panel)
+                       cursor_type = HUD_Panel_Check_Mouse_Pos(true);
+               if (cursor_type != CURSOR_NORMAL && !tab_panel) // mouse over a panel?
                        drawfill(panel_pos - '1 1 0' * panel_bg_border, panel_size + '2 2 0' * panel_bg_border, '1 1 1', .1, DRAWFLAG_NORMAL);
        }
-       // draw cursor after performing move/resize to have the panel pos/size updated before mouse_over_panel
-       float cursor_alpha = 1 - autocvar__menu_alpha;
-
-       if(!mouse_over_panel)
-               draw_cursor_normal(mousepos, '1 1 1', cursor_alpha);
-       else if(mouse_over_panel == 1)
-               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
-       else if(mouse_over_panel == 2)
-               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize", '1 1 1', cursor_alpha);
-       else
-               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize2", '1 1 1', cursor_alpha);
-
-       prevMouseClicked = mouseClicked;
 }
 void HUD_Configure_DrawGrid()
 {
@@ -1258,8 +1243,6 @@ void HUD_Configure_Frame()
 
                if(!hud_configure_prev)
                {
-                       if(autocvar_hud_cursormode)
-                               setcursormode(1);
                        hudShiftState = 0;
                        for(i = hud_panels_COUNT - 1; i >= 0; --i)
                                hud_panels_from(panel_order[i]).update_time = time;
@@ -1279,8 +1262,6 @@ void HUD_Configure_Frame()
        {
                if(hud_configure_menu_open)
                        hud_configure_menu_open = 0;
-               if(autocvar_hud_cursormode)
-                       setcursormode(0);
                hud_dynamic_shake_factor = -1;
        }
 }
index 7b7d82b4445c376ea46a019daf14ee2488b0c5dc..f63ffb1deab223f0c31425248acfbee784399a48 100644 (file)
@@ -118,7 +118,7 @@ void HUD_InfoMessages()
 
                        MUTATOR_CALLHOOK(DrawInfoMessages, pos, mySize);
 
-                       if(!warmup_stage && gametype == MAPINFO_TYPE_LMS)
+                       if(!warmup_stage && ISGAMETYPE(LMS))
                        {
                                entity sk;
                                sk = playerslots[player_localnum];
index 87e4a7fb251af875266a9ea72c82cdd6449e1f91..4d1691a7fd57798ac639b7023f6a04f81e476a36 100644 (file)
@@ -54,9 +54,9 @@ void HUD_Mod_CA(vector myPos, vector mySize)
        mod_active = 1; // required in each mod function that always shows something
 
        int layout;
-       if(gametype == MAPINFO_TYPE_CA)
+       if(ISGAMETYPE(CA))
                layout = autocvar_hud_panel_modicons_ca_layout;
-       else //if(gametype == MAPINFO_TYPE_FREEZETAG)
+       else //if(ISGAMETYPE(FREEZETAG))
                layout = autocvar_hud_panel_modicons_freezetag_layout;
        int rows, columns;
        float aspect_ratio;
@@ -528,7 +528,7 @@ void HUD_Mod_Race(vector pos, vector mySize)
 
        // clientside personal record
        string rr;
-       if(gametype == MAPINFO_TYPE_CTS)
+       if(ISGAMETYPE(CTS))
                rr = CTS_RECORD;
        else
                rr = RACE_RECORD;
index a6c65183d4541cc0cb23c1cdf133e594fc3c1e0c..aa77690a6a4a23ee36aa39548511289239ab8ad1 100644 (file)
@@ -18,7 +18,7 @@ void HUD_Physics()
        {
                if(!autocvar_hud_panel_physics) return;
                if(spectatee_status == -1 && (autocvar_hud_panel_physics == 1 || autocvar_hud_panel_physics == 3)) return;
-               if(autocvar_hud_panel_physics == 3 && !(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
+               if(autocvar_hud_panel_physics == 3 && !(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
        }
 
        HUD_Panel_LoadCvars();
index 947bfd53b3ee1075063b72a489ece3b981811753..49b7a97018c0071d4170e06fe43e8bd274d7d905 100644 (file)
@@ -103,7 +103,7 @@ void HUD_Powerups()
                addPowerupItem("Strength", "strength", autocvar_hud_progressbar_strength_color, strengthTime, 30);
        if(shieldTime)
                addPowerupItem("Shield", "shield", autocvar_hud_progressbar_shield_color, shieldTime, 30);
-       if(superTime)
+       if(superTime && !(allItems & IT_UNLIMITED_SUPERWEAPONS))
                addPowerupItem("Superweapons", "superweapons", autocvar_hud_progressbar_superweapons_color, superTime, 30);
 
        MUTATOR_CALLHOOK(HUD_Powerups_add);
index 98b15ee9537257443d0a19d06dba2beed6ce4c4a..29907505bceffef9edf1fb472f8554b1567fd5ea 100644 (file)
@@ -4,6 +4,7 @@
 #include <client/defs.qh>
 #include <client/miscfunctions.qh>
 #include <common/ent_cs.qh>
+#include <common/minigames/cl_minigames.qh>
 #include <client/hud/_mod.qh>
 #include <client/mapvoting.qh>
 
@@ -57,8 +58,21 @@ void QuickMenu_Page_ClearEntry(int i)
        QuickMenu_Page_Command_Type[i] = 0;
 }
 
+bool HUD_QuickMenu_Forbidden()
+{
+       return (mv_active
+               || (hud_configure_prev && hud_configure_prev != -1)
+               || HUD_MinigameMenu_IsOpened()
+               || (QuickMenu_TimeOut && time > QuickMenu_TimeOut));
+}
+
+// returns true if succeded, false otherwise
 bool QuickMenu_Open(string mode, string submenu, string file)
 {
+       QuickMenu_TimeOut = 0;
+       if (HUD_QuickMenu_Forbidden())
+               return false;
+
        int fh = -1;
        string s;
 
@@ -105,7 +119,7 @@ bool QuickMenu_Open(string mode, string submenu, string file)
                while((s = fgets(fh)) && QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES)
                {
                        // first skip invalid entries, so we don't check them anymore
-                       float argc;
+                       int argc;
                        argc = tokenize_console(s);
                        if(argc == 0 || argv(0) == "")
                                continue;
@@ -166,9 +180,6 @@ bool QuickMenu_Open(string mode, string submenu, string file)
        else
                QuickMenu_Page_Load("", 0);
 
-       hud_panel_quickmenu = 1;
-       if(autocvar_hud_cursormode)
-               setcursormode(1);
        hudShiftState = 0;
 
        QuickMenu_TimeOut = ((autocvar_hud_panel_quickmenu_time > 0) ? time + autocvar_hud_panel_quickmenu_time : 0);
@@ -192,14 +203,9 @@ void QuickMenu_Close()
        for (i = 0; i < QUICKMENU_MAXLINES; ++i)
                QuickMenu_Page_ClearEntry(i);
        QuickMenu_Page_Entries = 0;
-       hud_panel_quickmenu = 0;
        mouseClicked = 0;
        prevMouseClicked = 0;
        QuickMenu_Buffer_Close();
-
-       if(autocvar_hud_cursormode)
-       if(!mv_active)
-               setcursormode(0);
 }
 
 // It assumes submenu open tag is already detected
@@ -487,9 +493,6 @@ void QuickMenu_Mouse()
                return;
        }
 
-       if (!autocvar_hud_cursormode)
-               update_mousepos();
-
        panel = HUD_PANEL(QUICKMENU);
        HUD_Panel_LoadCvars();
 
@@ -525,10 +528,6 @@ void QuickMenu_Mouse()
                                QuickMenu_Page_ActiveEntry((entry_num < QUICKMENU_MAXLINES - 1) ? entry_num + 1 : 0);
                }
        }
-
-       draw_cursor_normal(mousepos, '1 1 1', 0.8);
-
-       prevMouseClicked = mouseClicked;
 }
 
 void HUD_Quickmenu_DrawEntry(vector pos, string desc, string option, vector fontsize)
@@ -571,16 +570,10 @@ void HUD_QuickMenu()
 {
        if(!autocvar__hud_configure)
        {
-               if (hud_configure_prev && hud_configure_prev != -1)
-                       QuickMenu_Close();
-
-               if(!hud_draw_maximized) return;
-               if(mv_active) return;
-               //if(!autocvar_hud_panel_quickmenu) return;
-               if(!hud_panel_quickmenu) return;
+               if (!hud_draw_maximized || !QuickMenu_IsOpened())
+                       return;
 
-               if(QuickMenu_TimeOut)
-               if(time > QuickMenu_TimeOut)
+               if (HUD_QuickMenu_Forbidden())
                {
                        QuickMenu_Close();
                        return;
index 6a190f2ca5f7c9b14c90590ed81f5227acc56bf0..7d09cf1ff5430276df551fe95c2025ab33258371 100644 (file)
@@ -98,7 +98,7 @@ void HUD_RaceTimer ()
        if(!autocvar__hud_configure)
        {
                if(!autocvar_hud_panel_racetimer) return;
-               if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
+               if(!(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
                if(spectatee_status == -1) return;
        }
 
index 65073d9fed09700f5c8aebc6dfd6c322f6a05294..cd4551725b3f90ba23440ef1db843a2fadd05f34 100644 (file)
@@ -27,8 +27,6 @@ void HUD_Radar_Show_Maximized(bool doshow, bool clickable)
        {
                if (clickable)
                {
-                       if(autocvar_hud_cursormode)
-                               setcursormode(1);
                        hud_panel_radar_mouse = 1;
 
                        // we must unset the player's buttons, as they aren't released elsewhere
@@ -43,9 +41,6 @@ void HUD_Radar_Show_Maximized(bool doshow, bool clickable)
        {
                hud_panel_radar_mouse = 0;
                mouseClicked = 0;
-               if(autocvar_hud_cursormode)
-               if(!mv_active)
-                       setcursormode(0);
        }
 }
 void HUD_Radar_Hide_Maximized()
@@ -141,9 +136,6 @@ void HUD_Radar_Mouse()
                return;
        }
 
-       if (!autocvar_hud_cursormode)
-               update_mousepos();
-
        panel = HUD_PANEL(RADAR);
        HUD_Panel_LoadCvars();
 
@@ -169,9 +161,6 @@ void HUD_Radar_Mouse()
                HUD_Radar_Hide_Maximized();
                return;
        }
-
-
-       draw_cursor_normal(mousepos, '1 1 1', 0.8);
 }
 
 void HUD_Radar()
@@ -354,7 +343,7 @@ void HUD_Radar()
        IL_EACH(g_radaricons, it.teamradar_icon, {
                if ( hud_panel_radar_mouse )
                if ( GetResourceAmount(it, RESOURCE_HEALTH) >= 0 )
-               if ( it.team == myteam + 1 || gametype == MAPINFO_TYPE_RACE || !teamplay )
+               if ( it.team == myteam + 1 || ISGAMETYPE(RACE) || !teamplay )
                {
                        vector coord = teamradar_texcoord_to_2dcoord(teamradar_3dcoord_to_texcoord(it.origin));
                        if(vdist((mousepos - coord), <, 8))
index 56fa5867c240f33484f9495d4818bde4b5197e51..525bf614b8be11e0cd01c075ec857b61c2937090 100644 (file)
@@ -140,7 +140,7 @@ void HUD_Score()
        if(!autocvar__hud_configure)
        {
                if(!autocvar_hud_panel_score) return;
-               if(spectatee_status == -1 && (gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
+               if(spectatee_status == -1 && (ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
        }
 
        HUD_Panel_LoadCvars();
index 32ccccfca3355dedfa235c20e96f316ee61ed2b4..4989aac508cdb221238a6593ad28fbfd677e7ccc 100644 (file)
@@ -61,6 +61,9 @@ float autocvar_hud_panel_scoreboard_namesize = 15;
 bool autocvar_hud_panel_scoreboard_accuracy = true;
 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
+float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
+float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
+
 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
 
 bool autocvar_hud_panel_scoreboard_dynamichud = false;
@@ -1025,6 +1028,12 @@ vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity
                        field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
                        drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
                }
+               if(pl.eliminated)
+               {
+                       h_size.x = column_width + hud_fontsize.x * 0.25;
+                       h_size.y = hud_fontsize.y;
+                       drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
+               }
                pos.x += column_width;
                pos.x += hud_fontsize.x;
        }
@@ -1136,7 +1145,7 @@ bool Scoreboard_WouldDraw()
                return true;
        else if (intermission == 2)
                return false;
-       else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
+       else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS) && !active_minigame)
                return true;
        else if (scoreboard_showscores_force)
                return true;
@@ -1146,6 +1155,15 @@ bool Scoreboard_WouldDraw()
 float average_accuracy;
 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
 {
+       if (frametime)
+       {
+               if (scoreboard_fade_alpha == 1)
+                       scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
+               else
+                       scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
+       }
+       vector initial_pos = pos;
+
        WepSet weapons_stat = WepSet_GetFromStat();
        WepSet weapons_inmap = WepSet_GetFromStat_InMap();
        int disownedcnt = 0;
@@ -1179,7 +1197,7 @@ vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
        float weapon_height = 29;
        float height = hud_fontsize.y + weapon_height;
 
-       drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+       drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
        pos.y += 1.25 * hud_fontsize.y;
        if(panel.current_panel_bg != "0")
                pos.y += panel_bg_border;
@@ -1187,7 +1205,11 @@ vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
        panel_pos = pos;
        panel_size.y = height * rows;
        panel_size.y += panel_bg_padding * 2;
+
+       float panel_bg_alpha_save = panel_bg_alpha;
+       panel_bg_alpha *= scoreboard_acc_fade_alpha;
        HUD_Panel_DrawBg();
+       panel_bg_alpha = panel_bg_alpha_save;
 
        vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
        if(panel.current_panel_bg != "0")
@@ -1205,18 +1227,18 @@ vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
        float weapon_width = tmp.x / columnns / rows;
 
        if (sbt_bg_alpha)
-               drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
+               drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
 
        if(sbt_highlight)
        {
                // column highlighting
                for (int i = 0; i < columnns; ++i)
                        if ((i % 2) == 0)
-                               drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
+                               drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
 
                // row highlighting
                for (int i = 0; i < rows; ++i)
-                       drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
+                       drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
        }
 
        average_accuracy = 0;
@@ -1249,7 +1271,7 @@ vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
                        weapon_alpha = 0.2 * sbt_fg_alpha;
 
                // weapon icon
-               drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
+               drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
                // the accuracy
                if (weapon_stats >= 0) {
                        weapons_with_stats += 1;
@@ -1264,7 +1286,7 @@ vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
                        if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
                                rgb = Accuracy_GetColor(weapon_stats);
 
-                       drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
+                       drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
                }
                tmpos.x += weapon_width * rows;
                pos.x += weapon_width * rows;
@@ -1280,7 +1302,10 @@ vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
                average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
 
        panel_size.x += panel_bg_padding * 2; // restore initial width
-       return end_pos;
+
+       if (scoreboard_acc_fade_alpha == 1)
+               return end_pos;
+       return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
 }
 
 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
@@ -1379,7 +1404,7 @@ vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_siz
        vector hl_rgb = rgb + '0.5 0.5 0.5';
 
        pos.y += hud_fontsize.y;
-       drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+       drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
        pos.y += 1.25 * hud_fontsize.y;
        if(panel.current_panel_bg != "0")
                pos.y += panel_bg_border;
@@ -1469,6 +1494,39 @@ vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_siz
        return end_pos;
 }
 
+float scoreboard_time;
+bool have_weapon_stats;
+bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
+{
+       if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
+               return false;
+       if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
+               return false;
+
+       if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
+               && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
+               && !intermission)
+       {
+               return false;
+       }
+
+       if (!have_weapon_stats)
+       {
+               FOREACH(Weapons, it != WEP_Null, {
+                       int weapon_stats = weapon_accuracy[i - WEP_FIRST];
+                       if (weapon_stats >= 0)
+                       {
+                               have_weapon_stats = true;
+                               break;
+                       }
+               });
+               if (!have_weapon_stats)
+                       return false;
+       }
+
+       return true;
+}
+
 void Scoreboard_Draw()
 {
        if(!autocvar__hud_configure)
@@ -1477,6 +1535,8 @@ void Scoreboard_Draw()
 
                // frametime checks allow to toggle the scoreboard even when the game is paused
                if(scoreboard_active) {
+                       if (scoreboard_fade_alpha < 1)
+                               scoreboard_time = time;
                        if(hud_configure_menu_open == 1)
                                scoreboard_fade_alpha = 1;
                        float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
@@ -1500,7 +1560,10 @@ void Scoreboard_Draw()
                }
 
                if (!scoreboard_fade_alpha)
+               {
+                       scoreboard_acc_fade_alpha = 0;
                        return;
+               }
        }
        else
                scoreboard_fade_alpha = 0;
@@ -1600,12 +1663,10 @@ void Scoreboard_Draw()
                pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
        }
 
-       bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
-
-       if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
+       if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
                pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
 
-       if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
+       if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
                if(race_speedaward) {
                        drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
                        pos.y += 1.25 * hud_fontsize.y;
@@ -1646,7 +1707,7 @@ void Scoreboard_Draw()
        tl = STAT(TIMELIMIT);
        fl = STAT(FRAGLIMIT);
        ll = STAT(LEADLIMIT);
-       if(gametype == MAPINFO_TYPE_LMS)
+       if(ISGAMETYPE(LMS))
        {
                if(tl > 0)
                        str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
index a560b74c74ab616e33fc4ac497741a6e22cf4f48..f585b480fe70b119f2491c3df712d5475aa2f3b3 100644 (file)
@@ -3,8 +3,9 @@
 
 bool scoreboard_active;
 float scoreboard_fade_alpha;
+float scoreboard_acc_fade_alpha;
 
-void Cmd_Scoreboard_SetFields(float argc);
+void Cmd_Scoreboard_SetFields(int argc);
 void Scoreboard_Draw();
 void Scoreboard_InitScores();
 void Scoreboard_UpdatePlayerTeams();
index 0dcbb70dba3cfe067d51160d74b2c26d8be70196..e01aa751757e3087b25038cdac422e6b162c4049 100644 (file)
@@ -63,6 +63,8 @@ void HUD_Timer()
 
        if (intermission_time) {
                timer = seconds_tostring(max(0, floor(intermission_time - STAT(GAMESTARTTIME))));
+       } else if (warmup_stage && warmup_timeleft >= 60) {
+               timer = _("WARMUP");
        } else if (autocvar_hud_panel_timer_increment || (!warmup_stage && timelimit == 0) || (warmup_stage && warmup_timeleft <= 0)) {
                if (time < STAT(GAMESTARTTIME))
                        timer = seconds_tostring(0); //while restart is still active, show 00:00
index 57b32039dc50fdf401d4da978413c0e4192a800f..0337eccfc215cac966713dd4602018548129b935 100644 (file)
@@ -9,7 +9,7 @@
 
 void HUD_Vote()
 {
-       if(autocvar_cl_allow_uid2name == -1 && (gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (serverflags & SERVERFLAG_PLAYERSTATS)))
+       if(autocvar_cl_allow_uid2name == -1 && (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (serverflags & SERVERFLAG_PLAYERSTATS)))
        {
                // this dialog gets overriden by the uid2name menu dialog, if it exists
                // TODO remove this client side uid2name dialog in the next release
index 863905a3d32fd4fb5ec1b4c9ae9e2d2dbf45df31..51324e1c5e09aa320895b35d1725ba572d21382d 100644 (file)
@@ -223,12 +223,14 @@ void Shutdown()
 
        localcmd("\ncl_hook_shutdown\n");
 
+       localcmd("\n-button14\n");
+
        deactivate_minigame();
        HUD_MinigameMenu_Close(NULL, NULL, NULL);
 }
 
 .float has_team;
-float SetTeam(entity o, int Team)
+bool SetTeam(entity o, int Team)
 {
        TC(int, Team);
        devassert_once(Team);
@@ -363,20 +365,21 @@ void PostInit()
 // In the case of mouse input after a setcursormode(1) call, nPrimary is xpos, nSecondary is ypos.
 float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
 {
-    TC(int, bInputType);
-       if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary))
+       TC(int, bInputType);
+       bool override = false;
+       override |= HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary);
+       if (override)
                return true;
 
-       if (QuickMenu_InputEvent(bInputType, nPrimary, nSecondary))
-               return true;
+       override |= QuickMenu_InputEvent(bInputType, nPrimary, nSecondary);
 
-       if (HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary))
-               return true;
+       override |= HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary);
 
-       if (MapVote_InputEvent(bInputType, nPrimary, nSecondary))
-               return true;
+       override |= MapVote_InputEvent(bInputType, nPrimary, nSecondary);
+
+       override |= HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary);
 
-       if (HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary))
+       if(override)
                return true;
 
        return false;
@@ -528,6 +531,7 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
                race_laptime = 0;
                race_checkpointtime = 0;
                hud_dynamic_shake_factor = -1;
+               spectatee_status_changed_time = time;
        }
        if (autocvar_hud_panel_healtharmor_progressbar_gfx)
        {
@@ -577,14 +581,14 @@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
        {
                for(j = 0; j < maxclients; ++j)
                        if(playerslots[j])
-                               playerslots[j].ready = 1;
+                               playerslots[j].ready = true;
                for(i = 1; i <= maxclients; i += 8)
                {
                        f = ReadByte();
                        for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j)
                                if (!(f & b))
                                        if(playerslots[j])
-                                               playerslots[j].ready = 0;
+                                               playerslots[j].ready = false;
                }
        }
 
@@ -605,7 +609,7 @@ NET_HANDLE(ENT_CLIENT_ELIMINATEDPLAYERS, bool isnew)
        if (sf & 1) {
                for (int j = 0; j < maxclients; ++j) {
                        if (playerslots[j]) {
-                               playerslots[j].eliminated = 1;
+                               playerslots[j].eliminated = true;
                        }
                }
                for (int i = 1; i <= maxclients; i += 8) {
@@ -615,7 +619,7 @@ NET_HANDLE(ENT_CLIENT_ELIMINATEDPLAYERS, bool isnew)
                                if (f & BIT(b)) continue;
                                int j = i - 1 + b;
                                if (playerslots[j]) {
-                                       playerslots[j].eliminated = 0;
+                                       playerslots[j].eliminated = false;
                                }
                        }
                }
index 69c3fa3d2b300eb4f54f6fd2a9bf4b8e68db62bf..f0f8f1d4bea5ad62563c20df43bc0ce6ad2c9f29 100644 (file)
@@ -9,8 +9,10 @@ vector mi_scale;
 // Minimap
 string minimapname;
 
-float postinit;
+bool postinit;
 entity gametype;
+// temporary hack
+#define ISGAMETYPE(NAME) (gametype == MAPINFO_TYPE_##NAME)
 
 float FONT_USER = 8;
 
@@ -27,7 +29,7 @@ void Ent_Remove(entity this);
 
 void Gamemode_Init();
 
-float SetTeam(entity pl, float Team);
+bool SetTeam(entity pl, int Team);
 
 vector hud_fontsize;
 
@@ -38,10 +40,10 @@ float grecordtime[RANKINGS_CNT];
 
 entity playerslots[255]; // 255 is engine limit on maxclients
 entity teamslots[17];    // 17 teams (including "spectator team")
-.float gotscores;
+.bool gotscores;
 .entity owner;
-.float ready;
-.float eliminated;
+.bool ready;
+.bool eliminated;
 
 .void(entity) draw;
 IntrusiveList g_drawables;
@@ -64,7 +66,7 @@ bool button_attack2;
 
 float current_viewzoom;
 float zoomin_effect;
-float warmup_stage;
+bool warmup_stage;
 
 void Fog_Force();
 
@@ -73,15 +75,16 @@ string _getcommandkey(string text, string command, bool forcename);
 #define getcommandkey_forcename(cmd_name, command) _getcommandkey(cmd_name, command, true)
 
 string vote_called_vote;
-float ready_waiting;
-float ready_waiting_for_me;
-float vote_waiting;
-float vote_waiting_for_me;
+bool ready_waiting;
+bool ready_waiting_for_me;
+bool vote_waiting;
+bool vote_waiting_for_me;
 
 float current_zoomfraction;
 
-float cs_project_is_b0rked;
-float vid_width, vid_height, vid_pixelheight;
+int cs_project_is_b0rked;
+int vid_width, vid_height;
+float vid_pixelheight;
 
 float camera_active;           // Demo camera is active if set to true
 float chase_active_backup;
index 03e94dc7f304238bc916443bc4a6fbef512d7bc7..37cb59e082101f645ac197f6c224c12715f62bfd 100644 (file)
@@ -321,6 +321,7 @@ float MapVote_Selection(vector topleft, vector cellsize, float rows, float colum
        return mv_mouse_selection;
 }
 
+vector prev_mousepos;
 void MapVote_Draw()
 {
        string map;
@@ -339,10 +340,11 @@ void MapVote_Draw()
 
        if (!autocvar_hud_cursormode)
        {
-               vector mpos = mousepos;
-               update_mousepos();
-               if (mpos.x != mousepos.x || mpos.y != mousepos.y)
+               if (mousepos.x != prev_mousepos.x || mousepos.y != prev_mousepos.y)
+               {
                        mv_selection_keyboard = 0;
+                       prev_mousepos = mousepos;
+               }
        }
 
        center = (vid_conwidth - 1)/2;
@@ -485,8 +487,6 @@ void MapVote_Draw()
                pos.x = (xmax+xmin)*0.5;
                MapVote_DrawAbstain(pos, dist.x, xmax - xmin, tmp, i);
        }
-
-       draw_cursor_normal(mousepos, '1 1 1', panel_fg_alpha);
 }
 
 void Cmd_MapVote_MapDownload(int argc)
@@ -649,8 +649,7 @@ void GameTypeVote_ReadOption(int i)
 void MapVote_Init()
 {
        mv_active = 1;
-       if(autocvar_hud_cursormode) setcursormode(1);
-       else mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
+       if(!autocvar_hud_cursormode) mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
        mv_selection = -1;
        mv_selection_keyboard = 0;
 
index 2f95102a990bf84571b8010cff9b55c674991dca..8a6f542a0a597277cb598b45dff23f335f2ae824 100644 (file)
@@ -3,7 +3,7 @@
 #include <common/constants.qh>
 void MapVote_Draw();
 
-void Cmd_MapVote_MapDownload(float argc);
+void Cmd_MapVote_MapDownload(int argc);
 
 float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary);
 
index 7c1ece5a3dc395c0afea7f4f2bfdfd8ab8519d4f..2fc1559494728343e27fed7b3282f2c37d668a0e 100644 (file)
@@ -38,8 +38,18 @@ const float SHOWNAMES_FADESPEED = 4;
 const float SHOWNAMES_FADEDELAY = 0.4;
 void Draw_ShowNames(entity this)
 {
-       if (this.sv_entnum == (current_player + 1))  // self or spectatee
-               if (!(autocvar_hud_shownames_self && autocvar_chase_active)) return;
+       if (this.sv_entnum == current_player + 1) // self or spectatee
+       {
+               if (!autocvar_chase_active)
+                       return;
+
+               if (!autocvar_hud_shownames_self
+                       && !(spectatee_status > 0 && time <= spectatee_status_changed_time + 1))
+               {
+                       return;
+               }
+       }
+
        if (!this.sameteam && !autocvar_hud_shownames_enemies) return;
        bool hit;
        if (!autocvar_hud_shownames_crosshairdistance && this.sameteam)
@@ -118,7 +128,7 @@ void Draw_ShowNames(entity this)
                // FIXME: alpha is negative when dead, breaking death fade
                if (!this.csqcmodel_isdead) a *= f;
        }
-       if (a < ALPHA_MIN_VISIBLE && gametype != MAPINFO_TYPE_CTS) return;
+       if (a < ALPHA_MIN_VISIBLE && ISGAMETYPE(CTS)) return;
        if (vdist(this.origin - view_origin, >=, max_shot_distance)) return;
        float dist = vlen(this.origin - view_origin);
        if (autocvar_hud_shownames_maxdistance)
index a7fbe136825b960bb2718fc518183d5d259d8e47..8fba190c64bfb32db161be60402cbbda6e8db29b 100644 (file)
@@ -48,6 +48,7 @@
 #define EFMASK_CHEAP (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NODRAW | EF_NOSHADOW | EF_SELECTABLE | EF_TELEPORT_BIT)
 
 float autocvar_cl_viewmodel_scale;
+float autocvar_cl_viewmodel_alpha;
 
 bool autocvar_cl_bobmodel;
 float autocvar_cl_bobmodel_speed;
@@ -297,12 +298,9 @@ void viewmodel_draw(entity this)
        if(!this.activeweapon || !autocvar_r_drawviewmodel)
                return;
        int mask = (intermission || (STAT(HEALTH) <= 0) || autocvar_chase_active) ? 0 : MASK_NORMAL;
-       float a = this.alpha;
-       static bool wasinvehicle;
+       float a = ((autocvar_cl_viewmodel_alpha) ? bound(-1, autocvar_cl_viewmodel_alpha, this.m_alpha) : this.m_alpha);
        bool invehicle = player_localentnum > maxclients;
        if (invehicle) a = -1;
-       else if (wasinvehicle) a = 1;
-       wasinvehicle = invehicle;
        Weapon wep = this.activeweapon;
        int c = entcs_GetClientColors(current_player);
        vector g = weaponentity_glowmod(wep, NULL, c, this);
@@ -427,6 +425,10 @@ void Porto_Draw(entity this)
 
                vector pos = view_origin;
                vector dir = view_forward;
+               makevectors(((autocvar_chase_active) ? warpzone_save_view_angles : view_angles));
+               pos += v_right * -wepent.movedir.y
+                       +  v_up * wepent.movedir.z;
+
                if (wepent.angles_held_status)
                {
                        makevectors(wepent.angles_held);
@@ -631,6 +633,8 @@ vector GetOrthoviewFOV(vector ov_worldmin, vector ov_worldmax, vector ov_mid, ve
 // this function must match W_SetupShot!
 float zoomscript_caught;
 
+bool minigame_wasactive;
+
 vector wcross_origin;
 float wcross_scale_prev, wcross_alpha_prev;
 vector wcross_color_prev;
@@ -799,34 +803,41 @@ vector liquidcolor_prev;
 
 float eventchase_current_distance;
 float eventchase_running;
-bool WantEventchase(entity this)
+int WantEventchase(entity this)
 {
        if(autocvar_cl_orthoview)
-               return false;
+               return 0;
        if(STAT(GAME_STOPPED) || intermission)
-               return true;
+               return 1;
        if(this.viewloc)
-               return true;
+               return 1;
        if(spectatee_status >= 0)
        {
                if(hud != HUD_NORMAL && (autocvar_cl_eventchase_vehicle || spectatee_status > 0))
-                       return true;
+                       return 1;
                if(MUTATOR_CALLHOOK(WantEventchase, this))
-                       return true;
+                       return 1;
                if(autocvar_cl_eventchase_frozen && STAT(FROZEN))
-                       return true;
+                       return 1;
                if(autocvar_cl_eventchase_death && (STAT(HEALTH) <= 0))
                {
                        if(autocvar_cl_eventchase_death == 2)
                        {
                                // don't stop eventchase once it's started (even if velocity changes afterwards)
                                if(this.velocity == '0 0 0' || eventchase_running)
-                                       return true;
+                                       return 1;
                        }
-                       else return true;
+                       else return 1;
+               }
+               if (spectatee_status > 0 && autocvar_cl_eventchase_spectated_change)
+               {
+                       if (time <= spectatee_status_changed_time + min(3, autocvar_cl_eventchase_spectated_change_time))
+                               return 1;
+                       else if (eventchase_running)
+                               return -1; // disable chase_active while eventchase is still enabled so to avoid a glicth
                }
        }
-       return false;
+       return 0;
 }
 
 void HUD_Crosshair_Vehicle(entity this)
@@ -1528,7 +1539,7 @@ void HUD_Draw(entity this)
        if(autocvar_r_letterbox == 0)
                if(autocvar_viewsize < 120)
                {
-                       if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS))
+                       if(!(ISGAMETYPE(RACE) || ISGAMETYPE(CTS)))
                                Accuracy_LoadLevels();
 
                        HUD_Main();
@@ -1555,6 +1566,51 @@ void ViewLocation_Mouse()
        //draw_cursor(viewloc_mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
 }
 
+void HUD_Cursor_Show()
+{
+       float cursor_alpha = 1 - autocvar__menu_alpha;
+       if(cursor_type == CURSOR_NORMAL)
+               draw_cursor_normal(mousepos, '1 1 1', cursor_alpha);
+       else if(cursor_type == CURSOR_MOVE)
+               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+       else if(cursor_type == CURSOR_RESIZE)
+               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize", '1 1 1', cursor_alpha);
+       else if(cursor_type == CURSOR_RESIZE2)
+               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize2", '1 1 1', cursor_alpha);
+}
+
+void HUD_Mouse(entity player)
+{
+       if(autocvar__menu_alpha == 1)
+               return;
+
+       if(!cursor_active)
+       {
+               if(player.viewloc && (player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+                       ViewLocation_Mouse(); // NOTE: doesn't use cursormode
+               return;
+       }
+
+       if(!autocvar_hud_cursormode)
+               update_mousepos();
+
+       if(autocvar__hud_configure)
+               HUD_Panel_Mouse();
+       else
+       {
+               if (HUD_MinigameMenu_IsOpened())
+                       HUD_Minigame_Mouse();
+               if (QuickMenu_IsOpened())
+                       QuickMenu_Mouse();
+               if (HUD_Radar_Clickable())
+                       HUD_Radar_Mouse();
+       }
+
+       prevMouseClicked = mouseClicked;
+
+       HUD_Cursor_Show();
+}
+
 bool ov_enabled;
 float oldr_nearclip;
 float oldr_farclip_base;
@@ -1691,7 +1747,8 @@ void CSQC_UpdateView(entity this, float w, float h)
                        }
                }
 
-               if(WantEventchase(this))
+               int eventchase = WantEventchase(this);
+               if (eventchase)
                {
                        vector current_view_origin_override = '0 0 0';
                        vector view_offset_override = '0 0 0';
@@ -1769,7 +1826,8 @@ void CSQC_UpdateView(entity this, float w, float h)
                        if(!local_player.viewloc)
                                setproperty(VF_ANGLES, WarpZone_TransformVAngles(WarpZone_trace_transform, view_angles));
                }
-               else if(autocvar_chase_active < 0) // time to disable chase_active if it was set by this code
+
+               if (eventchase <= 0 && autocvar_chase_active < 0) // time to disable chase_active if it was set by this code
                {
                        eventchase_running = false;
                        cvar_set("chase_active", "0");
@@ -1960,6 +2018,20 @@ void CSQC_UpdateView(entity this, float w, float h)
                }
        }
 
+       if(active_minigame && HUD_MinigameMenu_IsOpened())
+       {
+               if(!minigame_wasactive)
+               {
+                       localcmd("+button14\n");
+                       minigame_wasactive = true;
+               }
+       }
+       else if(minigame_wasactive)
+       {
+               localcmd("-button14\n");
+               minigame_wasactive = false;
+       }
+
        ColorTranslateMode = autocvar_cl_stripcolorcodes;
 
        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
@@ -2395,7 +2467,7 @@ void CSQC_UpdateView(entity this, float w, float h)
        else if(cvar("r_glsl_postprocess") == 2)
                cvar_set("r_glsl_postprocess", "0");
 
-       /*if(gametype == MAPINFO_TYPE_CTF)
+       /*if(ISGAMETYPE(CTF))
          {
          ctf_view();
          } else */
@@ -2444,16 +2516,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                cvar_set("vid_conheight", h0);
        }
 
-       if(autocvar__hud_configure)
-               HUD_Panel_Mouse();
-       else if (HUD_MinigameMenu_IsOpened() || active_minigame)
-               HUD_Minigame_Mouse();
-       else if(QuickMenu_IsOpened())
-               QuickMenu_Mouse();
-       else if(local_player.viewloc && (local_player.viewloc.spawnflags & VIEWLOC_FREEAIM))
-               ViewLocation_Mouse(); // NOTE: doesn't use cursormode
-       else
-               HUD_Radar_Mouse();
+       HUD_Mouse(local_player);
 
        cl_notice_run();
        unpause_update();
index 93af4255e2b95570cd5ab278b22af71110b8368e..5bce2fe3246818293474b6383dd05a2a1d68ce39 100644 (file)
@@ -15,3 +15,10 @@ void TrueAim_Init();
 entity viewmodels[MAX_WEAPONSLOTS];
 
 vector viewloc_mousepos;
+
+bool cursor_active;
+int cursor_type;
+const int CURSOR_NORMAL = 0;
+const int CURSOR_MOVE = 1;
+const int CURSOR_RESIZE = 2;
+const int CURSOR_RESIZE2 = 3;
index f4871e7fc6194803adf7fa2a8b9ec1276abc06c3..067c0badb7da09a184c192daf1841d47c782da81 100644 (file)
@@ -265,19 +265,19 @@ NET_HANDLE(ENT_CLIENT_PROJECTILE, bool isnew)
                        this.fade_rate = 0;
                }
 
-               int myteam = ReadByte();
-               this.team = myteam - 1;
+               int proj_team = ReadByte();
+               this.team = proj_team - 1;
 
                if(teamplay)
                {
-                       if(myteam)
+                       if(proj_team)
                                this.colormap = (this.team) * 0x11; // note: team - 1 on server (client uses different numbers)
                        else
                                this.colormap = 0x00;
                        this.colormap |= BIT(10); // RENDER_COLORMAPPED
                }
                else
-                       this.colormap = myteam;
+                       this.colormap = proj_team;
                // TODO: projectiles use glowmaps for their color, not teams
                #if 0
                if(this.colormap > 0)
index b53a9ba0e9cbf6123d7438736785e8206200358a..69fe458269845c7e0acf7b3eaa17c3008cd5581d 100644 (file)
@@ -210,9 +210,8 @@ vector animdecide_getloweranim(entity e)
                                return vec3(e.anim_duckwalkbackright.x, t, ANIMPRIO_CROUCH);
                        case ANIMIMPLICITSTATE_BACKWARDS | ANIMIMPLICITSTATE_LEFT:
                                return vec3(e.anim_duckwalkbackleft.x, t, ANIMPRIO_CROUCH);
-                       default:
-                               return vec3(e.anim_duckidle.x, t, ANIMPRIO_CROUCH);
                }
+               return vec3(e.anim_duckidle.x, t, ANIMPRIO_CROUCH);
        }
        else
        {
@@ -236,12 +235,11 @@ vector animdecide_getloweranim(entity e)
                                return vec3(e.anim_backright.x, t, ANIMPRIO_ACTIVE);
                        case ANIMIMPLICITSTATE_BACKWARDS | ANIMIMPLICITSTATE_LEFT:
                                return vec3(e.anim_backleft.x, t, ANIMPRIO_ACTIVE);
-                       default:
-                               return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
                }
+               return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
        }
        // can't get here
-       return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
+       //return vec3(e.anim_idle.x, t, ANIMPRIO_IDLE);
 }
 
 void animdecide_setimplicitstate(entity e, float onground)
index 3bdc8725c2803b5c9c27f6f0f30c9841d6ea8294..25c008d260d12329b2e16aed6dfdf97fdaf0973d 100644 (file)
@@ -29,4 +29,4 @@ void CampaignFile_Unload();
 
 // Sets up the campaign for the n-th array item (meaning: campaign_offset+nth
 // level) using localcmd()
-void CampaignSetup(float n);
+void CampaignSetup(int n);
index bc26203399bf44aace75bcff27b303f2caa738b0..4f099b53301010a383b9efaa5a27d9e2e5ceebaf 100644 (file)
@@ -52,8 +52,6 @@ float CampaignFile_Load(int offset, float n)
                a = ""; \
        else \
                ++i
-// What you're seeing here is what people will do when your compiler supports
-// C-style macros but no line continuations.
 
                                i = -1; // starts at -1 so I don't need postincrement; that is, i points to BEFORE the current arg!
                                CAMPAIGN_GETARG; campaign_gametype[campaign_entries] = strzone(a);
index c58a3df75a50d83e55731bc5955020f999781007..2cac0b1e5b99be949cf0fd7d3cdf9ed9ceca1787 100644 (file)
@@ -57,7 +57,7 @@ void Curl_URI_Get_Callback(int id, float status, string data)
 //  Command Sub-Functions
 // =======================
 
-void GenericCommand_addtolist(float request, float argc)
+void GenericCommand_addtolist(int request, int argc)
 {
        switch(request)
        {
@@ -97,7 +97,7 @@ void GenericCommand_addtolist(float request, float argc)
        }
 }
 
-void GenericCommand_qc_curl(float request, float argc)
+void GenericCommand_qc_curl(int request, int argc)
 {
        switch(request)
        {
@@ -169,7 +169,7 @@ void GenericCommand_qc_curl(float request, float argc)
        }
 }
 
-GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to *_cmd_dump.txt")
+GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to <program>_cmd_dump.txt")
 {
        switch(request)
        {
@@ -184,23 +184,28 @@ GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to *_cmd_dump.tx
                                #ifdef SVQC
                                        CMD_Write("dump of server console commands:\n");
                                        GameCommand_macro_write_aliases(fh);
+                                       CMD_Write("\n");
 
-                                       CMD_Write("\ndump of networked client only commands:\n");
+                                       CMD_Write("dump of networked client only commands:\n");
                                        ClientCommand_macro_write_aliases(fh);
+                                       CMD_Write("\n");
 
-                                       CMD_Write("\ndump of common commands:\n");
+                                       CMD_Write("dump of common commands:\n");
                                        CommonCommand_macro_write_aliases(fh);
+                                       CMD_Write("\n");
 
-                                       CMD_Write("\ndump of ban commands:\n");
+                                       CMD_Write("dump of ban commands:\n");
                                        BanCommand_macro_write_aliases(fh);
+                                       CMD_Write("\n");
                                #endif
 
                                #ifdef CSQC
                                        CMD_Write("dump of client commands:\n");
                                        LocalCommand_macro_write_aliases(fh);
+                                       CMD_Write("\n");
                                #endif
 
-                               CMD_Write("\ndump of generic commands:\n");
+                               CMD_Write("dump of generic commands:\n");
                                GenericCommand_macro_write_aliases(fh);
 
                                LOG_INFO("Completed dump of aliases in ^2data/data/", GetProgramCommandPrefix(), "_dump.txt^7.");
@@ -224,7 +229,7 @@ GENERIC_COMMAND(dumpcommands, "Dump all commands on the program to *_cmd_dump.tx
        }
 }
 
-void GenericCommand_maplist(float request, float argc)
+void GenericCommand_maplist(int request, int argc)
 {
        switch(request)
        {
@@ -307,7 +312,7 @@ void GenericCommand_maplist(float request, float argc)
        }
 }
 
-void GenericCommand_nextframe(float request, float arguments, string command)
+void GenericCommand_nextframe(int request, string command)
 {
        switch(request)
        {
@@ -327,7 +332,7 @@ void GenericCommand_nextframe(float request, float arguments, string command)
        }
 }
 
-void GenericCommand_removefromlist(float request, float argc)
+void GenericCommand_removefromlist(int request, int argc)
 {
        switch(request)
        {
@@ -362,7 +367,7 @@ void GenericCommand_removefromlist(float request, float argc)
        }
 }
 
-void GenericCommand_restartnotifs(float request)
+void GenericCommand_restartnotifs(int request)
 {
        switch(request)
        {
@@ -410,7 +415,7 @@ void GenericCommand_restartnotifs(float request)
        }
 }
 
-void GenericCommand_settemp(float request, float argc)
+void GenericCommand_settemp(int request, int argc)
 {
        switch(request)
        {
@@ -441,7 +446,7 @@ void GenericCommand_settemp(float request, float argc)
        }
 }
 
-void GenericCommand_settemp_restore(float request, float argc)
+void GenericCommand_settemp_restore(int request)
 {
        switch(request)
        {
@@ -468,7 +473,7 @@ void GenericCommand_settemp_restore(float request, float argc)
        }
 }
 
-void GenericCommand_runtest(float request, float argc)
+void GenericCommand_runtest(int request, int argc)
 {
        switch(request)
        {
@@ -496,7 +501,7 @@ void GenericCommand_runtest(float request, float argc)
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void GenericCommand_(float request)
+void GenericCommand_(int request)
 {
        switch(request)
        {
@@ -520,13 +525,13 @@ void GenericCommand_(float request)
 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
 GENERIC_COMMAND(addtolist, "Add a string to a cvar") { GenericCommand_addtolist(request, arguments); }
 GENERIC_COMMAND(maplist, "Automatic control of maplist") { GenericCommand_maplist(request, arguments); }
-GENERIC_COMMAND(nextframe, "Execute the given command next frame of this VM") { GenericCommand_nextframe(request, arguments, command); }
+GENERIC_COMMAND(nextframe, "Execute the given command next frame of this VM") { GenericCommand_nextframe(request, command); }
 GENERIC_COMMAND(qc_curl, "Queries a URL") { GenericCommand_qc_curl(request, arguments); }
 GENERIC_COMMAND(removefromlist, "Remove a string from a cvar") { GenericCommand_removefromlist(request, arguments); }
 GENERIC_COMMAND(restartnotifs, "Re-initialize all notifications") { GenericCommand_restartnotifs(request); }
 GENERIC_COMMAND(rpn, "RPN calculator") { GenericCommand_rpn(request, arguments, command); }
 GENERIC_COMMAND(settemp, "Temporarily set a value to a cvar which is restored later") { GenericCommand_settemp(request, arguments); }
-GENERIC_COMMAND(settemp_restore, "Restore all cvars set by settemp command") { GenericCommand_settemp_restore(request, arguments); }
+GENERIC_COMMAND(settemp_restore, "Restore all cvars set by settemp command") { GenericCommand_settemp_restore(request); }
 GENERIC_COMMAND(runtest, "Run unit tests") { GenericCommand_runtest(request, arguments); }
 
 void GenericCommand_macro_help()
@@ -534,7 +539,7 @@ void GenericCommand_macro_help()
        FOREACH(GENERIC_COMMANDS, true, LOG_INFOF("  ^2%s^7: %s", it.m_name, it.m_description));
 }
 
-float GenericCommand_macro_command(float argc, string command)
+float GenericCommand_macro_command(int argc, string command)
 {
        string c = strtolower(argv(0));
        FOREACH(GENERIC_COMMANDS, it.m_name == c, {
@@ -544,7 +549,7 @@ float GenericCommand_macro_command(float argc, string command)
        return false;
 }
 
-float GenericCommand_macro_usage(float argc)
+float GenericCommand_macro_usage(int argc)
 {
        string c = strtolower(argv(1));
        FOREACH(GENERIC_COMMANDS, it.m_name == c, {
@@ -567,7 +572,7 @@ void GenericCommand_macro_write_aliases(float fh)
 
 float GenericCommand(string command)
 {
-       float argc = tokenize_console(command);
+       int argc = tokenize_console(command);
        float n, j, f, i;
        string s, s2, c;
        vector rgb;
index b39c79901457de6a77b18d42ed93ac5113d71d61..68aa0ae88af0d1b0b1deafc9eee5f46735da808a 100644 (file)
@@ -9,9 +9,9 @@
 
 void GenericCommand_macro_help();
 
-float GenericCommand_macro_command(float argc, string command);
+float GenericCommand_macro_command(int argc, string command);
 
-float GenericCommand_macro_usage(float argc);
+float GenericCommand_macro_usage(int argc);
 
 void GenericCommand_macro_write_aliases(float fh);
 
index 7e1c4f52eb2879e58f47328c9402f33879398cd5..0998fad9dbd4ea4ea02ed815840ff18a8b3f548f 100644 (file)
@@ -53,7 +53,7 @@ float rpn_popf() { return stof(rpn_pop()); }
 void rpn_pushf(float f) { return rpn_push(sprintf("%.9g", f)); }
 void rpn_setf(float f) { return rpn_set(sprintf("%.9g", f)); }
 
-void GenericCommand_rpn(float request, float argc, string command)
+void GenericCommand_rpn(int request, int argc, string command)
 {
        switch(request)
        {
index ba028e248431806131679777bf116d8430d4f07a..75f5ba65d88ce2f06682fdce971dbd4671cda72d 100644 (file)
@@ -11,4 +11,4 @@ int rpn_error;
 int rpn_sp;
 string rpn_stack[MAX_RPN_STACK];
 
-void GenericCommand_rpn(float request, float argc, string command);
+void GenericCommand_rpn(int request, int argc, string command);
index 41c5d3017eada07992e8ba6ae102827da85525d4..b1dea6dcc431a84c1063d36f567047222c3d530c 100644 (file)
@@ -292,7 +292,7 @@ MUTATOR_HOOKFUNCTION(trace, SV_StartFrame)
                it.debug_trace_button = btn;
                if (!btn || skip) continue;
                FOREACH_ENTITY(true, {
-                   it.solid_prev = it.solid;
+                       it.solid_prev = it.solid;
                        it.solid = SOLID_BBOX;
                });
                vector forward = '0 0 0'; vector right = '0 0 0'; vector up = '0 0 0';
@@ -300,8 +300,8 @@ MUTATOR_HOOKFUNCTION(trace, SV_StartFrame)
                vector pos = it.origin + it.view_ofs;
                traceline(pos, pos + forward * max_shot_distance, MOVE_NORMAL, it);
                FOREACH_ENTITY(true, {
-                   it.solid = it.solid_prev;
-            it.solid_prev = 0;
+                       it.solid = it.solid_prev;
+                       it.solid_prev = 0;
                });
                entity e = trace_ent;
                int i = etof(e);
index 6439a49bb21ce32c6c64bbc8814f37dbb8f5520d..882e715cde3bf3673045499e643547a01a2d44bf 100644 (file)
@@ -229,6 +229,7 @@ EFFECT(0, SMOKING,                  "smoking")
 EFFECT(0, SMOKE_RING,               "smoke_ring")
 EFFECT(0, JUMPPAD,                  "jumppad_activate")
 EFFECT(1, BULLET,                   "tr_bullet")
+EFFECT(1, BULLET_WEAK,              "tr_bullet_weak")
 EFFECT(0, EF_FLAME,                 "EF_FLAME")
 EFFECT(0, EF_STARDUST,              "EF_STARDUST")
 EFFECT(0, TE_EXPLOSION,             "TE_EXPLOSION")
@@ -258,6 +259,6 @@ entity EFFECT_ROCKETMINSTA_LASER(int teamid)
         case NUM_TEAM_4:    e = EFFECT_ROCKETMINSTA_LASER_PINK; break;
         default:            e = EFFECT_ROCKETMINSTA_LASER_NEUTRAL; break;
     }
-    if (particleeffectnum(e) < 0 || Team_TeamToNumber(teamid) == -1) { e = EFFECT_TR_NEXUIZPLASMA; }
+    if (particleeffectnum(e) < 0 || !Team_IsValidTeam(teamid)) { e = EFFECT_TR_NEXUIZPLASMA; }
     return e;
 }
index b659e8a8517a83755718d93ea06c03c9a9989db8..d3c184fab743f78878f3c4d01fc5a42c34ea17a5 100644 (file)
@@ -1,3 +1,12 @@
+// docs: https://www.quakewiki.net/darkplaces-wiki/effectinfo-scripting-reference/
+// use `cl_particles_reloadeffects` to reload effects without restarting engine
+// use `chase_active 1` and `cl_lockview 1` to see effects from different perspectives
+// `dumpeffectinfo` currently doesn't work so edit effectinfo.txt manually, just try to keep the files in sync
+
+// `tex` are indices into particles/particlefont.tga (see particles/particlefont-template.tga for numbers)
+// the first index is inclusive, second exclusive (so `tex 0 8` will use images 0 though 7)
+// unless they're equal (`tex 69 69` is the same as `tex 69 70`)
+
 // item respawn effect
 DEF(TE_WIZSPIKE);
 // flare particle and light
@@ -4410,20 +4419,50 @@ SUB(flac_explode) {
 
 // bullet trail (somewhat like a tracer)
 DEF(tr_bullet);
+SUB(tr_bullet) {
+       MY(alpha) = '500 600 10000';
+       MY(color_min) = "0xf03000";
+       MY(color_max) = "0xff6010";
+       MY(countabsolute) = 1;
+       MY(sizeincrease) = -3;
+       MY(size_min) = 0.6;
+       MY(size_max) = 0.8;
+       my(tex_min) = 200;
+       my(tex_max) = 200;
+       MY(type) = "beam";
+}
+SUB(tr_bullet) {
+       MY(airfriction) = -4;
+       MY(alpha) = '256 256 350';
+       MY(color_min) = "0x202020";
+       MY(color_max) = "0x404040";
+       MY(notunderwater) = true;
+       MY(sizeincrease) = 0.4;
+       MY(size_min) = 1;
+       MY(size_max) = 2;
+       MY(tex_min) = 0;
+       MY(tex_max) = 8;
+       MY(trailspacing) = 16;
+       MY(type) = "smoke";
+       MY(velocityjitter) = '4 4 4';
+}
 SUB(tr_bullet) {
        MY(alpha_min) = 256;
        MY(alpha_max) = 256;
-       MY(alpha_fade) = 2560;
-       MY(color_min) = "0xff8960";
-       MY(color_max) = "0xff8533";
-       MY(size_min) = 4;
-       MY(size_max) = 4;
-       MY(stretchfactor) = 0.200000;
-       MY(tex_min) = 70;
-       MY(tex_max) = 70;
-       MY(trailspacing) = 750;
-       MY(type) = "spark";
-       MY(velocitymultiplier) = 3;
+       MY(alpha_fade) = 128;
+       MY(bounce) = 1.500000;
+       MY(color_min) = "0x404040";
+       MY(color_max) = "0x808080";
+       MY(gravity) = -0.125000;
+       MY(liquidfriction) = 4;
+       MY(size_min) = 0.5;
+       MY(size_max) = 0.6;
+       MY(tex_min) = 62;
+       MY(tex_max) = 62;
+       MY(trailspacing) = 16;
+       MY(type) = "bubble";
+       MY(underwater) = true;
+       MY(velocityjitter) = '16.0 16.0 16.0';
 }
 
 // smoke emitter for small pipes
@@ -8889,3 +8928,51 @@ SUB(arc_bolt_explode) {
        MY(velocityjitter) = '224.0 224.0 224.0';
        MY(velocityoffset) = '0.0 0.0 80.0';
 }
+
+// weak bullet trail (somewhat like a tracer)
+DEF(tr_bullet_weak);
+SUB(tr_bullet_weak) {
+       MY(alpha) = '75 100 3000';
+       MY(color_min) = "0xf03000";
+       MY(color_max) = "0xff6010";
+       MY(countabsolute) = 1;
+       MY(sizeincrease) = -3;
+       MY(size_min) = 0.6;
+       MY(size_max) = 0.8;
+       my(tex_min) = 200;
+       my(tex_max) = 200;
+       MY(type) = "beam";
+}
+SUB(tr_bullet_weak) {
+       MY(airfriction) = -4;
+       MY(alpha) = '256 256 350';
+       MY(color_min) = "0x202020";
+       MY(color_max) = "0x404040";
+       MY(notunderwater) = true;
+       MY(sizeincrease) = 0.4;
+       MY(size_min) = 1;
+       MY(size_max) = 2;
+       MY(tex_min) = 0;
+       MY(tex_max) = 8;
+       MY(trailspacing) = 16;
+       MY(type) = "smoke";
+       MY(velocityjitter) = '4 4 4';
+}
+SUB(tr_bullet_weak) {
+       MY(alpha_min) = 256;
+       MY(alpha_max) = 256;
+       MY(alpha_fade) = 128;
+       MY(bounce) = 1.500000;
+       MY(color_min) = "0x404040";
+       MY(color_max) = "0x808080";
+       MY(gravity) = -0.125000;
+       MY(liquidfriction) = 4;
+       MY(size_min) = 0.5;
+       MY(size_max) = 0.6;
+       MY(tex_min) = 62;
+       MY(tex_max) = 62;
+       MY(trailspacing) = 32;
+       MY(type) = "bubble";
+       MY(underwater) = true;
+       MY(velocityjitter) = '16.0 16.0 16.0';
+}
index c4cd002c782221fe420d38415c25fed570909a4d..a33ec87a01a34a5a8406f57aa3c3d52829cf3994 100644 (file)
@@ -6,6 +6,7 @@
 #include <common/gamemodes/gamemode/cts/_mod.inc>
 #include <common/gamemodes/gamemode/deathmatch/_mod.inc>
 #include <common/gamemodes/gamemode/domination/_mod.inc>
+#include <common/gamemodes/gamemode/duel/_mod.inc>
 #include <common/gamemodes/gamemode/freezetag/_mod.inc>
 #include <common/gamemodes/gamemode/invasion/_mod.inc>
 #include <common/gamemodes/gamemode/keepaway/_mod.inc>
index d7c1aa66cc35f6f3f8143031123542abf134a33c..ffd71d59d3f1092453b6d83f8048003693dfa531 100644 (file)
@@ -6,6 +6,7 @@
 #include <common/gamemodes/gamemode/cts/_mod.qh>
 #include <common/gamemodes/gamemode/deathmatch/_mod.qh>
 #include <common/gamemodes/gamemode/domination/_mod.qh>
+#include <common/gamemodes/gamemode/duel/_mod.qh>
 #include <common/gamemodes/gamemode/freezetag/_mod.qh>
 #include <common/gamemodes/gamemode/invasion/_mod.qh>
 #include <common/gamemodes/gamemode/keepaway/_mod.qh>
index 1deb03156ca2365f053334601e6bd200ceeb2fec..e03d3c52e6530922fc93af01093e6c389de56d80 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/assault/assault.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/assault/sv_assault.qc>
+#endif
index 38b426d4aef54c540ceefcc413ec7802e0b536dd..211daa89e3fba8f56711c4c56c0ec9959a837439 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/assault/assault.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/assault/sv_assault.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qc b/qcsrc/common/gamemodes/gamemode/assault/assault.qc
deleted file mode 100644 (file)
index 9601731..0000000
+++ /dev/null
@@ -1,648 +0,0 @@
-#include "assault.qh"
-
-// TODO: split into sv_assault
-#ifdef SVQC
-.entity sprite;
-#define AS_ROUND_DELAY 5
-
-IntrusiveList g_assault_destructibles;
-IntrusiveList g_assault_objectivedecreasers;
-IntrusiveList g_assault_objectives;
-STATIC_INIT(g_assault)
-{
-       g_assault_destructibles = IL_NEW();
-       g_assault_objectivedecreasers = IL_NEW();
-       g_assault_objectives = IL_NEW();
-}
-
-// random functions
-void assault_objective_use(entity this, entity actor, entity trigger)
-{
-       // activate objective
-       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
-       //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
-       //print("Activator is ", actor.classname, "\n");
-
-       IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
-       {
-               target_objective_decrease_activate(it);
-       });
-}
-
-vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
-{
-       float hlth = GetResourceAmount(this, RESOURCE_HEALTH);
-       if (hlth < 0 || hlth >= ASSAULT_VALUE_INACTIVE)
-               return '-1 0 0';
-       return current;
-}
-
-// reset this objective. Used when spawning an objective
-// and when a new round starts
-void assault_objective_reset(entity this)
-{
-       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
-}
-
-// decrease the health of targeted objectives
-void assault_objective_decrease_use(entity this, entity actor, entity trigger)
-{
-       if(actor.team != assault_attacker_team)
-       {
-               // wrong team triggered decrease
-               return;
-       }
-
-       if(trigger.assault_sprite)
-       {
-               WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
-               if(trigger.classname == "func_assault_destructible")
-                       trigger.sprite = NULL; // TODO: just unsetting it?!
-       }
-       else
-               return; // already activated! cannot activate again!
-
-       float hlth = GetResourceAmount(this.enemy, RESOURCE_HEALTH);
-       if (hlth < ASSAULT_VALUE_INACTIVE)
-       {
-               if (hlth - this.dmg > 0.5)
-               {
-                       GameRules_scoring_add_team(actor, SCORE, this.dmg);
-                       TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
-               }
-               else
-               {
-                       GameRules_scoring_add_team(actor, SCORE, hlth);
-                       GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
-                       SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
-
-                       if(this.enemy.message)
-                               FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
-
-                       SUB_UseTargets(this.enemy, this, trigger);
-               }
-       }
-}
-
-void assault_setenemytoobjective(entity this)
-{
-       IL_EACH(g_assault_objectives, it.targetname == this.target,
-       {
-               if(this.enemy == NULL)
-                       this.enemy = it;
-               else
-                       objerror(this, "more than one objective as target - fix the map!");
-               break;
-       });
-
-       if(this.enemy == NULL)
-               objerror(this, "no objective as target - fix the map!");
-}
-
-bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
-{
-       if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
-               return false;
-
-       return true;
-}
-
-void target_objective_decrease_activate(entity this)
-{
-       entity spr;
-       this.owner = NULL;
-       FOREACH_ENTITY_STRING(target, this.targetname,
-       {
-               if(it.assault_sprite != NULL)
-               {
-                       WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
-                       if(it.classname == "func_assault_destructible")
-                               it.sprite = NULL; // TODO: just unsetting it?!
-               }
-
-               spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
-               spr.assault_decreaser = this;
-               spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
-               spr.classname = "sprite_waypoint";
-               WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
-               if(it.classname == "func_assault_destructible")
-               {
-                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
-                       WaypointSprite_UpdateMaxHealth(spr, it.max_health);
-                       WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
-                       it.sprite = spr;
-               }
-               else
-                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
-       });
-}
-
-void target_objective_decrease_findtarget(entity this)
-{
-       assault_setenemytoobjective(this);
-}
-
-void target_assault_roundend_reset(entity this)
-{
-       //print("round end reset\n");
-       ++this.cnt; // up round counter
-       this.winning = false; // up round
-}
-
-void target_assault_roundend_use(entity this, entity actor, entity trigger)
-{
-       this.winning = 1; // round has been won by attackers
-}
-
-void assault_roundstart_use(entity this, entity actor, entity trigger)
-{
-       SUB_UseTargets(this, this, trigger);
-
-       //(Re)spawn all turrets
-       IL_EACH(g_turrets, true,
-       {
-               // Swap turret teams
-               if(it.team == NUM_TEAM_1)
-                       it.team = NUM_TEAM_2;
-               else
-                       it.team = NUM_TEAM_1;
-
-               // Doubles as teamchange
-               turret_respawn(it);
-       });
-}
-void assault_roundstart_use_this(entity this)
-{
-       assault_roundstart_use(this, NULL, NULL);
-}
-
-void assault_wall_think(entity this)
-{
-       if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
-       {
-               this.model = "";
-               this.solid = SOLID_NOT;
-       }
-       else
-       {
-               this.model = this.mdl;
-               this.solid = SOLID_BSP;
-       }
-
-       this.nextthink = time + 0.2;
-}
-
-// trigger new round
-// reset objectives, toggle spawnpoints, reset triggers, ...
-void assault_new_round(entity this)
-{
-       //bprint("ASSAULT: new round\n");
-
-       // up round counter
-       this.winning = this.winning + 1;
-
-       // swap attacker/defender roles
-       if(assault_attacker_team == NUM_TEAM_1)
-               assault_attacker_team = NUM_TEAM_2;
-       else
-               assault_attacker_team = NUM_TEAM_1;
-
-       IL_EACH(g_saved_team, !IS_CLIENT(it),
-       {
-               if(it.team_saved == NUM_TEAM_1)
-                       it.team_saved = NUM_TEAM_2;
-               else if(it.team_saved == NUM_TEAM_2)
-                       it.team_saved = NUM_TEAM_1;
-       });
-
-       // reset the level with a countdown
-       cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
-       ReadyRestart_force(); // sets game_starttime
-}
-
-entity as_round;
-.entity ent_winning;
-void as_round_think()
-{
-       game_stopped = false;
-       assault_new_round(as_round.ent_winning);
-       delete(as_round);
-       as_round = NULL;
-}
-
-// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win. Otherwise the defending team wins once the timelimit passes.
-int WinningCondition_Assault()
-{
-       if(as_round)
-               return WINNING_NO;
-
-       WinningConditionHelper(NULL); // set worldstatus
-
-       int status = WINNING_NO;
-       // as the timelimit has not yet passed just assume the defending team will win
-       if(assault_attacker_team == NUM_TEAM_1)
-       {
-               SetWinners(team, NUM_TEAM_2);
-       }
-       else
-       {
-               SetWinners(team, NUM_TEAM_1);
-       }
-
-       entity ent;
-       ent = find(NULL, classname, "target_assault_roundend");
-       if(ent)
-       {
-               if(ent.winning) // round end has been triggered by attacking team
-               {
-                       bprint("Assault: round completed.\n");
-                       SetWinners(team, assault_attacker_team);
-
-                       TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
-
-                       if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
-                       {
-                               status = WINNING_YES;
-                       }
-                       else
-                       {
-                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
-                               as_round = new(as_round);
-                               as_round.think = as_round_think;
-                               as_round.ent_winning = ent;
-                               as_round.nextthink = time + AS_ROUND_DELAY;
-                               game_stopped = true;
-
-                               // make sure timelimit isn't hit while the game is blocked
-                               if(autocvar_timelimit > 0)
-                               if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
-                                       cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
-                       }
-               }
-       }
-
-       return status;
-}
-
-// spawnfuncs
-spawnfunc(info_player_attacker)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.team = NUM_TEAM_1; // red, gets swapped every round
-       spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(info_player_defender)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.team = NUM_TEAM_2; // blue, gets swapped every round
-       spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(target_objective)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.classname = "target_objective";
-       IL_PUSH(g_assault_objectives, this);
-       this.use = assault_objective_use;
-       this.reset = assault_objective_reset;
-       this.reset(this);
-       this.spawn_evalfunc = target_objective_spawn_evalfunc;
-}
-
-spawnfunc(target_objective_decrease)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.classname = "target_objective_decrease";
-       IL_PUSH(g_assault_objectivedecreasers, this);
-
-       if(!this.dmg)
-               this.dmg = 101;
-
-       this.use = assault_objective_decrease_use;
-       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
-       this.max_health = ASSAULT_VALUE_INACTIVE;
-       this.enemy = NULL;
-
-       InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
-}
-
-// destructible walls that can be used to trigger target_objective_decrease
-bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
-{
-       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
-       float hlth = GetResourceAmount(targ, RESOURCE_HEALTH);
-       if (hlth <= 0 || hlth >= true_limit)
-               return false;
-
-       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
-       if(targ.sprite)
-       {
-               WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
-       }
-       func_breakable_colormod(targ);
-       return true;
-}
-
-spawnfunc(func_breakable);
-spawnfunc(func_assault_destructible)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.spawnflags = 3;
-       this.classname = "func_assault_destructible";
-       this.event_heal = destructible_heal;
-       IL_PUSH(g_assault_destructibles, this);
-
-       if(assault_attacker_team == NUM_TEAM_1)
-               this.team = NUM_TEAM_2;
-       else
-               this.team = NUM_TEAM_1;
-
-       spawnfunc_func_breakable(this);
-}
-
-spawnfunc(func_assault_wall)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.classname = "func_assault_wall";
-       this.mdl = this.model;
-       _setmodel(this, this.mdl);
-       this.solid = SOLID_BSP;
-       setthink(this, assault_wall_think);
-       this.nextthink = time;
-       InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
-}
-
-spawnfunc(target_assault_roundend)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.winning = 0; // round not yet won by attackers
-       this.classname = "target_assault_roundend";
-       this.use = target_assault_roundend_use;
-       this.cnt = 0; // first round
-       this.reset = target_assault_roundend_reset;
-}
-
-spawnfunc(target_assault_roundstart)
-{
-       if (!g_assault) { delete(this); return; }
-
-       assault_attacker_team = NUM_TEAM_1;
-       this.classname = "target_assault_roundstart";
-       this.use = assault_roundstart_use;
-       this.reset2 = assault_roundstart_use_this;
-       InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
-}
-
-// legacy bot code
-void havocbot_goalrating_ast_targets(entity this, float ratingscale)
-{
-       IL_EACH(g_assault_destructibles, it.bot_attack,
-       {
-               if (it.target == "")
-                       continue;
-
-               bool found = false;
-               entity destr = it;
-               IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
-               {
-                       float hlth = GetResourceAmount(it.enemy, RESOURCE_HEALTH);
-                       if (hlth > 0 && hlth < ASSAULT_VALUE_INACTIVE)
-                       {
-                               found = true;
-                               break;
-                       }
-               });
-
-               if(!found)
-                       continue;
-
-               vector p = 0.5 * (it.absmin + it.absmax);
-
-               // Find and rate waypoints around it
-               found = false;
-               entity best = NULL;
-               float bestvalue = 99999999999;
-               entity des = it;
-               for(float radius = 0; radius < 1500 && !found; radius += 500)
-               {
-                       FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
-                       {
-                               if(checkpvs(it.origin, des))
-                               {
-                                       found = true;
-                                       if(it.cnt < bestvalue)
-                                       {
-                                               best = it;
-                                               bestvalue = it.cnt;
-                                       }
-                               }
-                       });
-               }
-
-               if(best)
-               {
-               ///     dprint("waypoints around target were found\n");
-               //      te_lightning2(NULL, '0 0 0', best.origin);
-               //      te_knightspike(best.origin);
-
-                       navigation_routerating(this, best, ratingscale, 4000);
-                       best.cnt += 1;
-
-                       this.havocbot_attack_time = 0;
-
-                       if(checkpvs(this.origin + this.view_ofs, it))
-                       if(checkpvs(this.origin + this.view_ofs, best))
-                       {
-                       //      dprint("increasing attack time for this target\n");
-                               this.havocbot_attack_time = time + 2;
-                       }
-               }
-       });
-}
-
-void havocbot_role_ast_offense(entity this)
-{
-       if(IS_DEAD(this))
-       {
-               this.havocbot_attack_time = 0;
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 120;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       if(this.havocbot_attack_time>time)
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
-               havocbot_goalrating_ast_targets(this, 20000);
-               havocbot_goalrating_items(this, 15000, this.origin, 10000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ast_defense(entity this)
-{
-       if(IS_DEAD(this))
-       {
-               this.havocbot_attack_time = 0;
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 120;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       if(this.havocbot_attack_time>time)
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
-               havocbot_goalrating_ast_targets(this, 20000);
-               havocbot_goalrating_items(this, 15000, this.origin, 10000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ast_setrole(entity this, float role)
-{
-       switch(role)
-       {
-               case HAVOCBOT_AST_ROLE_DEFENSE:
-                       this.havocbot_role = havocbot_role_ast_defense;
-                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
-                       this.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_AST_ROLE_OFFENSE:
-                       this.havocbot_role = havocbot_role_ast_offense;
-                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
-                       this.havocbot_role_timeout = 0;
-                       break;
-       }
-}
-
-void havocbot_ast_reset_role(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if(this.team == assault_attacker_team)
-               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
-       else
-               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.team == assault_attacker_team)
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
-       else
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
-}
-
-MUTATOR_HOOKFUNCTION(as, TurretSpawn)
-{
-       entity turret = M_ARGV(0, entity);
-
-       if(!turret.team || turret.team == FLOAT_MAX)
-               turret.team = 5; // this gets reversed when match starts?
-}
-
-MUTATOR_HOOKFUNCTION(as, VehicleInit)
-{
-       entity veh = M_ARGV(0, entity);
-
-       veh.nextthink = time + 0.5;
-}
-
-MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       havocbot_ast_reset_role(bot);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, PlayHitsound)
-{
-       entity frag_victim = M_ARGV(0, entity);
-
-       return (frag_victim.classname == "func_assault_destructible");
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
-{
-       // assault always has 2 teams
-       c1 = c2 = 0;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckRules_World)
-{
-       M_ARGV(0, float) = WinningCondition_Assault();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
-{
-       // incompatible
-       warmup_stage = 0;
-       sv_ready_restart_after_countdown = 0;
-}
-
-MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
-{
-    entity ent = M_ARGV(0, entity);
-
-       switch(ent.classname)
-       {
-               case "info_player_team1":
-               case "info_player_team2":
-               case "info_player_team3":
-               case "info_player_team4":
-                       return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
-{
-       // readyrestart not supported (yet)
-       return true;
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qh b/qcsrc/common/gamemodes/gamemode/assault/assault.qh
deleted file mode 100644 (file)
index d949f18..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <common/scores.qh>
-
-const int ASSAULT_VALUE_INACTIVE = 1000;
-
-const int ST_ASSAULT_OBJECTIVES = 1;
-
-REGISTER_MUTATOR(as, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-        GameRules_teams(true);
-        int teams = BITS(2); // always red vs blue
-        GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
-            field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
-            field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
-        });
-       }
-       return 0;
-}
-
-// sprites
-.entity assault_decreaser;
-.entity assault_sprite;
-
-// legacy bot defs
-const int HAVOCBOT_AST_ROLE_NONE = 0;
-const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
-const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
-
-.int havocbot_role_flags;
-.float havocbot_attack_time;
-
-void(entity this) havocbot_role_ast_defense;
-void(entity this) havocbot_role_ast_offense;
-
-void(entity bot) havocbot_ast_reset_role;
-
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-// predefined spawnfuncs
-void target_objective_decrease_activate(entity this);
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc b/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc
new file mode 100644 (file)
index 0000000..95dd412
--- /dev/null
@@ -0,0 +1,645 @@
+#include "sv_assault.qh"
+
+.entity sprite;
+#define AS_ROUND_DELAY 5
+
+IntrusiveList g_assault_destructibles;
+IntrusiveList g_assault_objectivedecreasers;
+IntrusiveList g_assault_objectives;
+STATIC_INIT(g_assault)
+{
+       g_assault_destructibles = IL_NEW();
+       g_assault_objectivedecreasers = IL_NEW();
+       g_assault_objectives = IL_NEW();
+}
+
+// random functions
+void assault_objective_use(entity this, entity actor, entity trigger)
+{
+       // activate objective
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
+       //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
+       //print("Activator is ", actor.classname, "\n");
+
+       IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
+       {
+               target_objective_decrease_activate(it);
+       });
+}
+
+vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
+{
+       float hlth = GetResourceAmount(this, RESOURCE_HEALTH);
+       if (hlth < 0 || hlth >= ASSAULT_VALUE_INACTIVE)
+               return '-1 0 0';
+       return current;
+}
+
+// reset this objective. Used when spawning an objective
+// and when a new round starts
+void assault_objective_reset(entity this)
+{
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+}
+
+// decrease the health of targeted objectives
+void assault_objective_decrease_use(entity this, entity actor, entity trigger)
+{
+       if(actor.team != assault_attacker_team)
+       {
+               // wrong team triggered decrease
+               return;
+       }
+
+       if(trigger.assault_sprite)
+       {
+               WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
+               if(trigger.classname == "func_assault_destructible")
+                       trigger.sprite = NULL; // TODO: just unsetting it?!
+       }
+       else
+               return; // already activated! cannot activate again!
+
+       float hlth = GetResourceAmount(this.enemy, RESOURCE_HEALTH);
+       if (hlth < ASSAULT_VALUE_INACTIVE)
+       {
+               if (hlth - this.dmg > 0.5)
+               {
+                       GameRules_scoring_add_team(actor, SCORE, this.dmg);
+                       TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
+               }
+               else
+               {
+                       GameRules_scoring_add_team(actor, SCORE, hlth);
+                       GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
+                       SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
+
+                       if(this.enemy.message)
+                               FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
+
+                       SUB_UseTargets(this.enemy, this, trigger);
+               }
+       }
+}
+
+void assault_setenemytoobjective(entity this)
+{
+       IL_EACH(g_assault_objectives, it.targetname == this.target,
+       {
+               if(this.enemy == NULL)
+                       this.enemy = it;
+               else
+                       objerror(this, "more than one objective as target - fix the map!");
+               break;
+       });
+
+       if(this.enemy == NULL)
+               objerror(this, "no objective as target - fix the map!");
+}
+
+bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
+{
+       if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
+               return false;
+
+       return true;
+}
+
+void target_objective_decrease_activate(entity this)
+{
+       entity spr;
+       this.owner = NULL;
+       FOREACH_ENTITY_STRING(target, this.targetname,
+       {
+               if(it.assault_sprite != NULL)
+               {
+                       WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
+                       if(it.classname == "func_assault_destructible")
+                               it.sprite = NULL; // TODO: just unsetting it?!
+               }
+
+               spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
+               spr.assault_decreaser = this;
+               spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
+               spr.classname = "sprite_waypoint";
+               WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
+               if(it.classname == "func_assault_destructible")
+               {
+                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
+                       WaypointSprite_UpdateMaxHealth(spr, it.max_health);
+                       WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
+                       it.sprite = spr;
+               }
+               else
+                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
+       });
+}
+
+void target_objective_decrease_findtarget(entity this)
+{
+       assault_setenemytoobjective(this);
+}
+
+void target_assault_roundend_reset(entity this)
+{
+       //print("round end reset\n");
+       ++this.cnt; // up round counter
+       this.winning = false; // up round
+}
+
+void target_assault_roundend_use(entity this, entity actor, entity trigger)
+{
+       this.winning = 1; // round has been won by attackers
+}
+
+void assault_roundstart_use(entity this, entity actor, entity trigger)
+{
+       SUB_UseTargets(this, this, trigger);
+
+       //(Re)spawn all turrets
+       IL_EACH(g_turrets, true,
+       {
+               // Swap turret teams
+               if(it.team == NUM_TEAM_1)
+                       it.team = NUM_TEAM_2;
+               else
+                       it.team = NUM_TEAM_1;
+
+               // Doubles as teamchange
+               turret_respawn(it);
+       });
+}
+void assault_roundstart_use_this(entity this)
+{
+       assault_roundstart_use(this, NULL, NULL);
+}
+
+void assault_wall_think(entity this)
+{
+       if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
+       {
+               this.model = "";
+               this.solid = SOLID_NOT;
+       }
+       else
+       {
+               this.model = this.mdl;
+               this.solid = SOLID_BSP;
+       }
+
+       this.nextthink = time + 0.2;
+}
+
+// trigger new round
+// reset objectives, toggle spawnpoints, reset triggers, ...
+void assault_new_round(entity this)
+{
+       //bprint("ASSAULT: new round\n");
+
+       // up round counter
+       this.winning = this.winning + 1;
+
+       // swap attacker/defender roles
+       if(assault_attacker_team == NUM_TEAM_1)
+               assault_attacker_team = NUM_TEAM_2;
+       else
+               assault_attacker_team = NUM_TEAM_1;
+
+       IL_EACH(g_saved_team, !IS_CLIENT(it),
+       {
+               if(it.team_saved == NUM_TEAM_1)
+                       it.team_saved = NUM_TEAM_2;
+               else if(it.team_saved == NUM_TEAM_2)
+                       it.team_saved = NUM_TEAM_1;
+       });
+
+       // reset the level with a countdown
+       cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
+       ReadyRestart_force(); // sets game_starttime
+}
+
+entity as_round;
+.entity ent_winning;
+void as_round_think()
+{
+       game_stopped = false;
+       assault_new_round(as_round.ent_winning);
+       delete(as_round);
+       as_round = NULL;
+}
+
+// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win. Otherwise the defending team wins once the timelimit passes.
+int WinningCondition_Assault()
+{
+       if(as_round)
+               return WINNING_NO;
+
+       WinningConditionHelper(NULL); // set worldstatus
+
+       int status = WINNING_NO;
+       // as the timelimit has not yet passed just assume the defending team will win
+       if(assault_attacker_team == NUM_TEAM_1)
+       {
+               SetWinners(team, NUM_TEAM_2);
+       }
+       else
+       {
+               SetWinners(team, NUM_TEAM_1);
+       }
+
+       entity ent;
+       ent = find(NULL, classname, "target_assault_roundend");
+       if(ent)
+       {
+               if(ent.winning) // round end has been triggered by attacking team
+               {
+                       bprint("Assault: round completed.\n");
+                       SetWinners(team, assault_attacker_team);
+
+                       TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
+
+                       if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
+                       {
+                               status = WINNING_YES;
+                       }
+                       else
+                       {
+                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
+                               as_round = new(as_round);
+                               as_round.think = as_round_think;
+                               as_round.ent_winning = ent;
+                               as_round.nextthink = time + AS_ROUND_DELAY;
+                               game_stopped = true;
+
+                               // make sure timelimit isn't hit while the game is blocked
+                               if(autocvar_timelimit > 0)
+                               if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
+                                       cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
+                       }
+               }
+       }
+
+       return status;
+}
+
+// spawnfuncs
+spawnfunc(info_player_attacker)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.team = NUM_TEAM_1; // red, gets swapped every round
+       spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(info_player_defender)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.team = NUM_TEAM_2; // blue, gets swapped every round
+       spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(target_objective)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.classname = "target_objective";
+       IL_PUSH(g_assault_objectives, this);
+       this.use = assault_objective_use;
+       this.reset = assault_objective_reset;
+       this.reset(this);
+       this.spawn_evalfunc = target_objective_spawn_evalfunc;
+}
+
+spawnfunc(target_objective_decrease)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.classname = "target_objective_decrease";
+       IL_PUSH(g_assault_objectivedecreasers, this);
+
+       if(!this.dmg)
+               this.dmg = 101;
+
+       this.use = assault_objective_decrease_use;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+       this.max_health = ASSAULT_VALUE_INACTIVE;
+       this.enemy = NULL;
+
+       InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       float hlth = GetResourceAmount(targ, RESOURCE_HEALTH);
+       if (hlth <= 0 || hlth >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       if(targ.sprite)
+       {
+               WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+       }
+       func_breakable_colormod(targ);
+       return true;
+}
+
+spawnfunc(func_breakable);
+spawnfunc(func_assault_destructible)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.spawnflags = 3;
+       this.classname = "func_assault_destructible";
+       this.event_heal = destructible_heal;
+       IL_PUSH(g_assault_destructibles, this);
+
+       if(assault_attacker_team == NUM_TEAM_1)
+               this.team = NUM_TEAM_2;
+       else
+               this.team = NUM_TEAM_1;
+
+       spawnfunc_func_breakable(this);
+}
+
+spawnfunc(func_assault_wall)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.classname = "func_assault_wall";
+       this.mdl = this.model;
+       _setmodel(this, this.mdl);
+       this.solid = SOLID_BSP;
+       setthink(this, assault_wall_think);
+       this.nextthink = time;
+       InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
+}
+
+spawnfunc(target_assault_roundend)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.winning = 0; // round not yet won by attackers
+       this.classname = "target_assault_roundend";
+       this.use = target_assault_roundend_use;
+       this.cnt = 0; // first round
+       this.reset = target_assault_roundend_reset;
+}
+
+spawnfunc(target_assault_roundstart)
+{
+       if (!g_assault) { delete(this); return; }
+
+       assault_attacker_team = NUM_TEAM_1;
+       this.classname = "target_assault_roundstart";
+       this.use = assault_roundstart_use;
+       this.reset2 = assault_roundstart_use_this;
+       InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
+}
+
+// legacy bot code
+void havocbot_goalrating_ast_targets(entity this, float ratingscale)
+{
+       IL_EACH(g_assault_destructibles, it.bot_attack,
+       {
+               if (it.target == "")
+                       continue;
+
+               bool found = false;
+               entity destr = it;
+               IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
+               {
+                       float hlth = GetResourceAmount(it.enemy, RESOURCE_HEALTH);
+                       if (hlth > 0 && hlth < ASSAULT_VALUE_INACTIVE)
+                       {
+                               found = true;
+                               break;
+                       }
+               });
+
+               if(!found)
+                       continue;
+
+               vector p = 0.5 * (it.absmin + it.absmax);
+
+               // Find and rate waypoints around it
+               found = false;
+               entity best = NULL;
+               float bestvalue = FLOAT_MAX;
+               entity des = it;
+               for (float radius = 500; radius <= 1500 && !found; radius += 500)
+               {
+                       FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
+                       {
+                               if(checkpvs(it.origin, des))
+                               {
+                                       found = true;
+                                       if(it.cnt < bestvalue)
+                                       {
+                                               best = it;
+                                               bestvalue = it.cnt;
+                                       }
+                               }
+                       });
+               }
+
+               if(best)
+               {
+               ///     dprint("waypoints around target were found\n");
+               //      te_lightning2(NULL, '0 0 0', best.origin);
+               //      te_knightspike(best.origin);
+
+                       navigation_routerating(this, best, ratingscale, 4000);
+                       best.cnt += 1;
+
+                       this.havocbot_attack_time = 0;
+
+                       if(checkpvs(this.origin + this.view_ofs, it))
+                       if(checkpvs(this.origin + this.view_ofs, best))
+                       {
+                       //      dprint("increasing attack time for this target\n");
+                               this.havocbot_attack_time = time + 2;
+                       }
+               }
+       });
+}
+
+void havocbot_role_ast_offense(entity this)
+{
+       if(IS_DEAD(this))
+       {
+               this.havocbot_attack_time = 0;
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 120;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       if(this.havocbot_attack_time>time)
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               // role: offense
+               navigation_goalrating_start(this);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
+               havocbot_goalrating_ast_targets(this, 20000);
+               havocbot_goalrating_items(this, 30000, this.origin, 10000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ast_defense(entity this)
+{
+       if(IS_DEAD(this))
+       {
+               this.havocbot_attack_time = 0;
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 120;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       if(this.havocbot_attack_time>time)
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               // role: defense
+               navigation_goalrating_start(this);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 3000);
+               havocbot_goalrating_ast_targets(this, 20000);
+               havocbot_goalrating_items(this, 30000, this.origin, 10000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ast_setrole(entity this, float role)
+{
+       switch(role)
+       {
+               case HAVOCBOT_AST_ROLE_DEFENSE:
+                       this.havocbot_role = havocbot_role_ast_defense;
+                       this.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_AST_ROLE_OFFENSE:
+                       this.havocbot_role = havocbot_role_ast_offense;
+                       this.havocbot_role_timeout = 0;
+                       break;
+       }
+}
+
+void havocbot_ast_reset_role(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if(this.team == assault_attacker_team)
+               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
+       else
+               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.team == assault_attacker_team)
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
+       else
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
+}
+
+MUTATOR_HOOKFUNCTION(as, TurretSpawn)
+{
+       entity turret = M_ARGV(0, entity);
+
+       if(!turret.team || turret.team == FLOAT_MAX)
+               turret.team = 5; // this gets reversed when match starts?
+}
+
+MUTATOR_HOOKFUNCTION(as, VehicleInit)
+{
+       entity veh = M_ARGV(0, entity);
+
+       veh.nextthink = time + 0.5;
+}
+
+MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       havocbot_ast_reset_role(bot);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, PlayHitsound)
+{
+       entity frag_victim = M_ARGV(0, entity);
+
+       return (frag_victim.classname == "func_assault_destructible");
+}
+
+MUTATOR_HOOKFUNCTION(as, TeamBalance_CheckAllowedTeams)
+{
+       // assault always has 2 teams
+       M_ARGV(0, float) = BIT(0) | BIT(1);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckRules_World)
+{
+       M_ARGV(0, float) = WinningCondition_Assault();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
+{
+       // incompatible
+       warmup_stage = 0;
+       sv_ready_restart_after_countdown = 0;
+}
+
+MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
+{
+    entity ent = M_ARGV(0, entity);
+
+       switch(ent.classname)
+       {
+               case "info_player_team1":
+               case "info_player_team2":
+               case "info_player_team3":
+               case "info_player_team4":
+                       return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
+{
+       // readyrestart not supported (yet)
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qh b/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qh
new file mode 100644 (file)
index 0000000..fcfc789
--- /dev/null
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+
+const int ASSAULT_VALUE_INACTIVE = 1000;
+
+const int ST_ASSAULT_OBJECTIVES = 1;
+
+REGISTER_MUTATOR(as, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+        GameRules_teams(true);
+        int teams = BITS(2); // always red vs blue
+        GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
+            field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+            field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+        });
+       }
+       return 0;
+}
+
+// sprites
+.entity assault_decreaser;
+.entity assault_sprite;
+
+// legacy bot defs
+const int HAVOCBOT_AST_ROLE_NONE = 0;
+const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
+const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
+
+.float havocbot_attack_time;
+
+void(entity this) havocbot_role_ast_defense;
+void(entity this) havocbot_role_ast_offense;
+
+void(entity bot) havocbot_ast_reset_role;
+
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+// assault game mode: Which team is attacking in this round?
+float assault_attacker_team;
+
+// predefined spawnfuncs
+void target_objective_decrease_activate(entity this);
index 57dc9b3dd3c83aa92becba730a7c2d40ebc2c07c..ce7b59399ab0816d5a9a9a8e0a14673d9b1acd73 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/clanarena/clanarena.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/clanarena/sv_clanarena.qc>
+#endif
index 66f23740af7289fbf9245d0ba01b1b7483c65fa4..55789f77ab3f395bd523d69d1cb5c91273a88646 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/clanarena/clanarena.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/clanarena/sv_clanarena.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc
deleted file mode 100644 (file)
index f4aea43..0000000
+++ /dev/null
@@ -1,492 +0,0 @@
-#include "clanarena.qh"
-
-// TODO: split into sv_clanarena
-#ifdef SVQC
-float autocvar_g_ca_damage2score_multiplier;
-bool autocvar_g_ca_spectate_enemies;
-
-void CA_count_alive_players()
-{
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               switch(it.team)
-               {
-                       case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
-                       case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
-                       case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
-                       case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
-               }
-       });
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               STAT(REDALIVE, it) = redalive;
-               STAT(BLUEALIVE, it) = bluealive;
-               STAT(YELLOWALIVE, it) = yellowalive;
-               STAT(PINKALIVE, it) = pinkalive;
-       });
-}
-
-float CA_GetWinnerTeam()
-{
-       float winner_team = 0;
-       if(redalive >= 1)
-               winner_team = NUM_TEAM_1;
-       if(bluealive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no player left
-}
-
-void nades_Clear(entity player);
-
-#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
-float CA_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-               FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
-               allowed_to_spawn = false;
-               game_stopped = true;
-               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-               return 1;
-       }
-
-       CA_count_alive_players();
-       if(CA_ALIVE_TEAMS() > 1)
-               return 0;
-
-       int winner_team = CA_GetWinnerTeam();
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       allowed_to_spawn = false;
-       game_stopped = true;
-       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-
-       FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
-       return 1;
-}
-
-void CA_RoundStart()
-{
-       allowed_to_spawn = boolean(warmup_stage);
-}
-
-bool CA_CheckTeams()
-{
-       static int prev_missing_teams_mask;
-       allowed_to_spawn = true;
-       CA_count_alive_players();
-       if(CA_ALIVE_TEAMS_OK())
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return true;
-       }
-       if(total_players == 0)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return false;
-       }
-       int missing_teams_mask = 0;
-       if(ca_teams & BIT(0))
-               missing_teams_mask += (!redalive) * 1;
-       if(ca_teams & BIT(1))
-               missing_teams_mask += (!bluealive) * 2;
-       if(ca_teams & BIT(2))
-               missing_teams_mask += (!yellowalive) * 4;
-       if(ca_teams & BIT(3))
-               missing_teams_mask += (!pinkalive) * 8;
-       if(prev_missing_teams_mask != missing_teams_mask)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-               prev_missing_teams_mask = missing_teams_mask;
-       }
-       return false;
-}
-
-bool ca_isEliminated(entity e)
-{
-       if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_LMS_LOSER))
-               return true;
-       if(e.caplayer == 0.5)
-               return true;
-       return false;
-}
-
-/** Returns next available player to spectate if g_ca_spectate_enemies == 0 */
-entity CA_SpectateNext(entity player, entity start)
-{
-       if (SAME_TEAM(start, player)) return start;
-       // continue from current player
-       for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
-       {
-               if (SAME_TEAM(player, e)) return e;
-       }
-       // restart from begining
-       for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
-       {
-               if (SAME_TEAM(player, e)) return e;
-       }
-       return start;
-}
-
-
-MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       player.caplayer = 1;
-       if (!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       // spectators / observers that weren't playing can join; they are
-       // immediately forced to observe in the PutClientInServer hook
-       // this way they are put in a team and can play in the next round
-       if (!allowed_to_spawn && player.caplayer)
-               return true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
-       {
-               TRANSMUTE(Observer, player);
-               if (CS(player).jointime != time && !player.caplayer) // not when connecting
-               {
-                       player.caplayer = 0.5;
-                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_players)
-{
-       FOREACH_CLIENT(true, {
-               CS(it).killcount = 0;
-               if (!it.caplayer && IS_BOT_CLIENT(it))
-               {
-                       it.team = -1;
-                       it.caplayer = 1;
-               }
-               if (it.caplayer)
-               {
-                       TRANSMUTE(Player, it);
-                       it.caplayer = 1;
-                       PutClientInServer(it);
-               }
-       });
-       bot_relinkplayerlist();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       TRANSMUTE(Observer, player);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_global)
-{
-       allowed_to_spawn = true;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = ca_teams;
-}
-
-entity ca_LastPlayerForTeam(entity this)
-{
-       entity last_pl = NULL;
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
-               if (!IS_DEAD(it))
-               if (SAME_TEAM(this, it))
-               if (!last_pl)
-                       last_pl = it;
-               else
-                       return NULL;
-       });
-       return last_pl;
-}
-
-void ca_LastPlayerForTeam_Notify(entity this)
-{
-       if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
-       {
-               entity pl = ca_LastPlayerForTeam(this);
-               if (pl)
-                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       ca_LastPlayerForTeam_Notify(frag_target);
-       if (!allowed_to_spawn)
-       {
-               frag_target.respawn_flags = RESPAWN_SILENT;
-               // prevent unwanted sudden rejoin as spectator and movement of spectator camera
-               frag_target.respawn_time = time + 2;
-       }
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-       if (!warmup_stage)
-       {
-               eliminatedPlayers.SendFlags |= 1;
-               if (IS_BOT_CLIENT(frag_target))
-                       bot_clear(frag_target);
-       }
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       if (player.caplayer == 1)
-               ca_LastPlayerForTeam_Notify(player);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if (!IS_DEAD(player))
-               ca_LastPlayerForTeam_Notify(player);
-       if (player.killindicator_teamchange == -2) // player wants to spectate
-               player.caplayer = 0;
-       if (player.caplayer)
-               player.frags = FRAGS_LMS_LOSER;
-       if (!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-       if (!player.caplayer)
-               return false;  // allow team reset
-       return true;  // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetStartItems)
-{
-       start_items       &= ~IT_UNLIMITED_AMMO;
-       start_health       = warmup_start_health       = cvar("g_lms_start_health");
-       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
-       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
-       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
-       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
-       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
-       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
-       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
-}
-
-MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_deathtype = M_ARGV(3, float);
-       float frag_damage = M_ARGV(4, float);
-       float frag_mirrordamage = M_ARGV(5, float);
-
-       if (IS_PLAYER(frag_target))
-       if (!IS_DEAD(frag_target))
-       if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
-               frag_damage = 0;
-
-       frag_mirrordamage = 0;
-
-       M_ARGV(4, float) = frag_damage;
-       M_ARGV(5, float) = frag_mirrordamage;
-}
-
-MUTATOR_HOOKFUNCTION(ca, FilterItem)
-{
-       entity item = M_ARGV(0, entity);
-
-       if (autocvar_g_powerups <= 0)
-       if (item.flags & FL_POWERUP)
-               return true;
-
-       if (autocvar_g_pickup_items <= 0)
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(7, float);
-       float damage_take = M_ARGV(4, float);
-       float damage_save = M_ARGV(5, float);
-
-       float excess = max(0, frag_damage - damage_take - damage_save);
-
-       if (frag_target != frag_attacker && IS_PLAYER(frag_attacker))
-               GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
-}
-
-MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
-{
-       // no respawn calculations needed, player is forced to spectate anyway
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
-{
-       // no regeneration in CA
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateSet)
-{
-       entity client = M_ARGV(0, entity);
-       entity targ = M_ARGV(1, entity);
-
-       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
-       if (DIFF_TEAM(targ, client))
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateNext)
-{
-       entity client = M_ARGV(0, entity);
-
-       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
-       {
-               entity targ = M_ARGV(1, entity);
-               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
-{
-       entity client = M_ARGV(0, entity);
-       entity targ = M_ARGV(1, entity);
-       entity first = M_ARGV(2, entity);
-
-       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
-       {
-               do { targ = targ.chain; }
-               while(targ && DIFF_TEAM(targ, client));
-
-               if (!targ)
-               {
-                       for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
-
-                       if (targ == client.enemy)
-                               return MUT_SPECPREV_RETURN;
-               }
-       }
-
-       M_ARGV(1, entity) = targ;
-
-       return MUT_SPECPREV_FOUND;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               if (IS_PLAYER(it) || it.caplayer == 1)
-                       ++M_ARGV(0, int);
-               ++M_ARGV(1, int);
-       });
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
-{
-       entity player = M_ARGV(0, entity);
-
-       if (player.caplayer)
-       {
-               // they're going to spec, we can do other checks
-               if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
-                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
-               return MUT_SPECCMD_FORCE;
-       }
-
-       return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(ca, WantWeapon)
-{
-       M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
-{
-       return true; // doesn't work well with the whole spectator as player thing
-}
-
-MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
-{
-       entity player = M_ARGV(0, entity);
-
-       return player.caplayer == 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
-{
-       // most weapons arena
-       if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh
deleted file mode 100644 (file)
index 8a94acd..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <server/round_handler.qh>
-#include <server/miscfunctions.qh>
-
-int autocvar_g_ca_point_limit;
-int autocvar_g_ca_point_leadlimit;
-float autocvar_g_ca_round_timelimit;
-bool autocvar_g_ca_team_spawns;
-//int autocvar_g_ca_teams;
-int autocvar_g_ca_teams_override;
-float autocvar_g_ca_warmup;
-
-
-int ca_teams;
-bool allowed_to_spawn;
-
-const int ST_CA_ROUNDS = 1;
-
-bool CA_CheckTeams();
-bool CA_CheckWinner();
-void CA_RoundStart();
-bool ca_isEliminated(entity e);
-
-REGISTER_MUTATOR(ca, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_ca_team_spawns);
-        GameRules_limit_score(autocvar_g_ca_point_limit);
-        GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
-
-               ca_teams = autocvar_g_ca_teams_override;
-               if (ca_teams < 2)
-                       ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
-
-               ca_teams = BITS(bound(2, ca_teams, 4));
-        GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
-            field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
-        });
-
-               allowed_to_spawn = true;
-               round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
-               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-               EliminatedPlayers_Init(ca_isEliminated);
-       }
-       return 0;
-}
-
-// should be removed in the future, as other code should not have to care
-.float caplayer; // 0.5 if scheduled to join the next round
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qc b/qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qc
new file mode 100644 (file)
index 0000000..2bbed4a
--- /dev/null
@@ -0,0 +1,503 @@
+#include "sv_clanarena.qh"
+
+float autocvar_g_ca_damage2score_multiplier;
+bool autocvar_g_ca_spectate_enemies;
+
+void CA_count_alive_players()
+{
+       total_players = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+       }
+       FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+       {
+               ++total_players;
+               if (IS_DEAD(it))
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(it);
+               int num_alive = Team_GetNumberOfAlivePlayers(team_);
+               ++num_alive;
+               Team_SetNumberOfAlivePlayers(team_, num_alive);
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+                       1));
+               STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(2));
+               STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(3));
+               STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(4));
+       });
+}
+
+int CA_GetWinnerTeam()
+{
+       int winner_team = 0;
+       if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
+       {
+               winner_team = NUM_TEAM_1;
+       }
+       for (int i = 2; i <= NUM_TEAMS; ++i)
+       {
+               if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
+       }
+       if (winner_team)
+       {
+               return winner_team;
+       }
+       return -1; // no player left
+}
+
+void nades_Clear(entity player);
+
+#define CA_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(ca_teams))
+float CA_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+               allowed_to_spawn = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+               return 1;
+       }
+
+       CA_count_alive_players();
+       if (Team_GetNumberOfAliveTeams() > 1)
+       {
+               return 0;
+       }
+
+       int winner_team = CA_GetWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       allowed_to_spawn = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+
+       FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+       return 1;
+}
+
+void CA_RoundStart()
+{
+       allowed_to_spawn = boolean(warmup_stage);
+}
+
+bool CA_CheckTeams()
+{
+       static int prev_missing_teams_mask;
+       allowed_to_spawn = true;
+       CA_count_alive_players();
+       if(CA_ALIVE_TEAMS_OK())
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return true;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return false;
+       }
+       int missing_teams_mask = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if ((ca_teams & Team_IndexToBit(i)) &&
+                       (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+               {
+                       missing_teams_mask |= Team_IndexToBit(i);
+               }
+       }
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+               prev_missing_teams_mask = missing_teams_mask;
+       }
+       return false;
+}
+
+bool ca_isEliminated(entity e)
+{
+       if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_LMS_LOSER))
+               return true;
+       if(e.caplayer == 0.5)
+               return true;
+       return false;
+}
+
+/** Returns next available player to spectate if g_ca_spectate_enemies == 0 */
+entity CA_SpectateNext(entity player, entity start)
+{
+       if (SAME_TEAM(start, player)) return start;
+       // continue from current player
+       for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
+       {
+               if (SAME_TEAM(player, e)) return e;
+       }
+       // restart from begining
+       for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
+       {
+               if (SAME_TEAM(player, e)) return e;
+       }
+       return start;
+}
+
+
+MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       player.caplayer = 1;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       // spectators / observers that weren't playing can join; they are
+       // immediately forced to observe in the PutClientInServer hook
+       // this way they are put in a team and can play in the next round
+       if (!allowed_to_spawn && player.caplayer)
+               return true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
+       {
+               TRANSMUTE(Observer, player);
+               if (CS(player).jointime != time && !player.caplayer) // not when connecting
+               {
+                       player.caplayer = 0.5;
+                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               CS(it).killcount = 0;
+               if (!it.caplayer && IS_BOT_CLIENT(it))
+               {
+                       it.team = -1;
+                       it.caplayer = 1;
+               }
+               if (it.caplayer)
+               {
+                       TRANSMUTE(Player, it);
+                       it.caplayer = 1;
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       TRANSMUTE(Observer, player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_global)
+{
+       allowed_to_spawn = true;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = ca_teams;
+       return true;
+}
+
+entity ca_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if (!IS_DEAD(it))
+               if (SAME_TEAM(this, it))
+               if (!last_pl)
+                       last_pl = it;
+               else
+                       return NULL;
+       });
+       return last_pl;
+}
+
+void ca_LastPlayerForTeam_Notify(entity this)
+{
+       if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+       {
+               entity pl = ca_LastPlayerForTeam(this);
+               if (pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       ca_LastPlayerForTeam_Notify(frag_target);
+       if (!allowed_to_spawn)
+       {
+               frag_target.respawn_flags = RESPAWN_SILENT;
+               // prevent unwanted sudden rejoin as spectator and movement of spectator camera
+               frag_target.respawn_time = time + 2;
+       }
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       if (!warmup_stage)
+       {
+               eliminatedPlayers.SendFlags |= 1;
+               if (IS_BOT_CLIENT(frag_target))
+                       bot_clear(frag_target);
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               ca_LastPlayerForTeam_Notify(player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               ca_LastPlayerForTeam_Notify(player);
+       if (player.killindicator_teamchange == -2) // player wants to spectate
+               player.caplayer = 0;
+       if (player.caplayer)
+               player.frags = FRAGS_LMS_LOSER;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       if (!player.caplayer)
+               return false;  // allow team reset
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetStartItems)
+{
+       start_items       &= ~IT_UNLIMITED_AMMO;
+       start_health       = warmup_start_health       = cvar("g_lms_start_health");
+       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
+       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
+       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
+       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
+       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
+       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
+}
+
+MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+       float frag_damage = M_ARGV(4, float);
+       float frag_mirrordamage = M_ARGV(5, float);
+
+       if (IS_PLAYER(frag_target))
+       if (!IS_DEAD(frag_target))
+       if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
+               frag_damage = 0;
+
+       frag_mirrordamage = 0;
+
+       M_ARGV(4, float) = frag_damage;
+       M_ARGV(5, float) = frag_mirrordamage;
+}
+
+MUTATOR_HOOKFUNCTION(ca, FilterItem)
+{
+       entity item = M_ARGV(0, entity);
+
+       if (autocvar_g_powerups <= 0)
+       if (item.flags & FL_POWERUP)
+               return true;
+
+       if (autocvar_g_pickup_items <= 0)
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(7, float);
+       float damage_take = bound(0, M_ARGV(4, float), GetResourceAmount(frag_target, RESOURCE_HEALTH));
+       float damage_save = bound(0, M_ARGV(5, float), GetResourceAmount(frag_target, RESOURCE_ARMOR));
+
+       float excess = max(0, frag_damage - damage_take - damage_save);
+
+       if (frag_target != frag_attacker && IS_PLAYER(frag_attacker) && DIFF_TEAM(frag_target, frag_attacker))
+               GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
+}
+
+MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
+{
+       // no respawn calculations needed, player is forced to spectate anyway
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
+{
+       // no regeneration in CA
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateSet)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+       if (DIFF_TEAM(targ, client))
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateNext)
+{
+       entity client = M_ARGV(0, entity);
+
+       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+       {
+               entity targ = M_ARGV(1, entity);
+               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+       entity first = M_ARGV(2, entity);
+
+       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+       {
+               do { targ = targ.chain; }
+               while(targ && DIFF_TEAM(targ, client));
+
+               if (!targ)
+               {
+                       for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
+
+                       if (targ == client.enemy)
+                               return MUT_SPECPREV_RETURN;
+               }
+       }
+
+       M_ARGV(1, entity) = targ;
+
+       return MUT_SPECPREV_FOUND;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               if (IS_PLAYER(it) || it.caplayer == 1)
+                       ++M_ARGV(0, int);
+               ++M_ARGV(1, int);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (player.caplayer)
+       {
+               // they're going to spec, we can do other checks
+               if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
+                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
+               return MUT_SPECCMD_FORCE;
+       }
+
+       return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(ca, WantWeapon)
+{
+       M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
+{
+       return true; // doesn't work well with the whole spectator as player thing
+}
+
+MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
+{
+       entity player = M_ARGV(0, entity);
+
+       return player.caplayer == 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
+{
+       // most weapons arena
+       if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
+}
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qh b/qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qh
new file mode 100644 (file)
index 0000000..c4755fe
--- /dev/null
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <server/round_handler.qh>
+#include <server/miscfunctions.qh>
+
+int autocvar_g_ca_point_limit;
+int autocvar_g_ca_point_leadlimit;
+float autocvar_g_ca_round_timelimit;
+bool autocvar_g_ca_team_spawns;
+//int autocvar_g_ca_teams;
+int autocvar_g_ca_teams_override;
+float autocvar_g_ca_warmup;
+
+
+int ca_teams;
+bool allowed_to_spawn;
+
+const int ST_CA_ROUNDS = 1;
+
+bool CA_CheckTeams();
+bool CA_CheckWinner();
+void CA_RoundStart();
+bool ca_isEliminated(entity e);
+
+REGISTER_MUTATOR(ca, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_ca_team_spawns);
+        GameRules_limit_score(autocvar_g_ca_point_limit);
+        GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
+
+               ca_teams = autocvar_g_ca_teams_override;
+               if (ca_teams < 2)
+                       ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+               ca_teams = BITS(bound(2, ca_teams, 4));
+        GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
+            field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
+        });
+
+               allowed_to_spawn = true;
+               round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
+               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+               EliminatedPlayers_Init(ca_isEliminated);
+       }
+       return 0;
+}
+
+// should be removed in the future, as other code should not have to care
+.float caplayer; // 0.5 if scheduled to join the next round
index dcd813569f98935894097c2202f02df3d13bd243..7bc5a9679c2a56a6c9d4278e648ed70c8647b408 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/ctf/ctf.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/ctf/sv_ctf.qc>
+#endif
index c1ddd9731ca231f3e633bb69606a7c5fe211c055..e7fcea7bcf0fed7cd4ac697f5368da35d848e3ee 100644 (file)
@@ -1,2 +1,5 @@
 // generated file; do not modify
 #include <common/gamemodes/gamemode/ctf/ctf.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/ctf/sv_ctf.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc
deleted file mode 100644 (file)
index 3f96b41..0000000
+++ /dev/null
@@ -1,2779 +0,0 @@
-#include "ctf.qh"
-
-// TODO: split into sv_ctf
-#ifdef SVQC
-#include <common/effects/all.qh>
-#include <common/vehicles/all.qh>
-#include <server/teamplay.qh>
-
-#include <lib/warpzone/common.qh>
-
-bool autocvar_g_ctf_allow_vehicle_carry;
-bool autocvar_g_ctf_allow_vehicle_touch;
-bool autocvar_g_ctf_allow_monster_touch;
-bool autocvar_g_ctf_throw;
-float autocvar_g_ctf_throw_angle_max;
-float autocvar_g_ctf_throw_angle_min;
-int autocvar_g_ctf_throw_punish_count;
-float autocvar_g_ctf_throw_punish_delay;
-float autocvar_g_ctf_throw_punish_time;
-float autocvar_g_ctf_throw_strengthmultiplier;
-float autocvar_g_ctf_throw_velocity_forward;
-float autocvar_g_ctf_throw_velocity_up;
-float autocvar_g_ctf_drop_velocity_up;
-float autocvar_g_ctf_drop_velocity_side;
-bool autocvar_g_ctf_oneflag_reverse;
-bool autocvar_g_ctf_portalteleport;
-bool autocvar_g_ctf_pass;
-float autocvar_g_ctf_pass_arc;
-float autocvar_g_ctf_pass_arc_max;
-float autocvar_g_ctf_pass_directional_max;
-float autocvar_g_ctf_pass_directional_min;
-float autocvar_g_ctf_pass_radius;
-float autocvar_g_ctf_pass_wait;
-bool autocvar_g_ctf_pass_request;
-float autocvar_g_ctf_pass_turnrate;
-float autocvar_g_ctf_pass_timelimit;
-float autocvar_g_ctf_pass_velocity;
-bool autocvar_g_ctf_dynamiclights;
-float autocvar_g_ctf_flag_collect_delay;
-float autocvar_g_ctf_flag_damageforcescale;
-bool autocvar_g_ctf_flag_dropped_waypoint;
-bool autocvar_g_ctf_flag_dropped_floatinwater;
-bool autocvar_g_ctf_flag_glowtrails;
-int autocvar_g_ctf_flag_health;
-bool autocvar_g_ctf_flag_return;
-bool autocvar_g_ctf_flag_return_carrying;
-float autocvar_g_ctf_flag_return_carried_radius;
-float autocvar_g_ctf_flag_return_time;
-bool autocvar_g_ctf_flag_return_when_unreachable;
-float autocvar_g_ctf_flag_return_damage;
-float autocvar_g_ctf_flag_return_damage_delay;
-float autocvar_g_ctf_flag_return_dropped;
-float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
-float autocvar_g_ctf_flagcarrier_auto_helpme_time;
-float autocvar_g_ctf_flagcarrier_selfdamagefactor;
-float autocvar_g_ctf_flagcarrier_selfforcefactor;
-float autocvar_g_ctf_flagcarrier_damagefactor;
-float autocvar_g_ctf_flagcarrier_forcefactor;
-//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
-bool autocvar_g_ctf_fullbrightflags;
-bool autocvar_g_ctf_ignore_frags;
-bool autocvar_g_ctf_score_ignore_fields;
-int autocvar_g_ctf_score_capture;
-int autocvar_g_ctf_score_capture_assist;
-int autocvar_g_ctf_score_kill;
-int autocvar_g_ctf_score_penalty_drop;
-int autocvar_g_ctf_score_penalty_returned;
-int autocvar_g_ctf_score_pickup_base;
-int autocvar_g_ctf_score_pickup_dropped_early;
-int autocvar_g_ctf_score_pickup_dropped_late;
-int autocvar_g_ctf_score_return;
-float autocvar_g_ctf_shield_force;
-float autocvar_g_ctf_shield_max_ratio;
-int autocvar_g_ctf_shield_min_negscore;
-bool autocvar_g_ctf_stalemate;
-int autocvar_g_ctf_stalemate_endcondition;
-float autocvar_g_ctf_stalemate_time;
-bool autocvar_g_ctf_reverse;
-float autocvar_g_ctf_dropped_capture_delay;
-float autocvar_g_ctf_dropped_capture_radius;
-
-void ctf_FakeTimeLimit(entity e, float t)
-{
-       msg_entity = e;
-       WriteByte(MSG_ONE, 3); // svc_updatestat
-       WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
-       if(t < 0)
-               WriteCoord(MSG_ONE, autocvar_timelimit);
-       else
-               WriteCoord(MSG_ONE, (t + 1) / 60);
-}
-
-void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
-               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ctf_CaptureRecord(entity flag, entity player)
-{
-       float cap_record = ctf_captimerecord;
-       float cap_time = (time - flag.ctf_pickuptime);
-       string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
-
-       // notify about shit
-       if(ctf_oneflag)
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
-       else if(!ctf_captimerecord)
-               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
-       else if(cap_time < cap_record)
-               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
-
-       // write that shit in the database
-       if(!ctf_oneflag) // but not in 1-flag mode
-       if((!ctf_captimerecord) || (cap_time < cap_record))
-       {
-               ctf_captimerecord = cap_time;
-               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
-               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
-               write_recordmarker(player, flag.ctf_pickuptime, cap_time);
-       }
-
-       if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
-               race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
-}
-
-bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
-{
-       int num_perteam = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
-
-       // automatically return if there's only 1 player on the team
-       return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
-               && flag.team);
-}
-
-bool ctf_Return_Customize(entity this, entity client)
-{
-       // only to the carrier
-       return boolean(client == this.owner);
-}
-
-void ctf_FlagcarrierWaypoints(entity player)
-{
-       WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
-       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
-       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-       WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
-
-       if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
-       {
-               if(!player.wps_enemyflagcarrier)
-               {
-                       entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
-                       wp.colormod = WPCOLOR_ENEMYFC(player.team);
-                       setcefc(wp, ctf_Stalemate_Customize);
-
-                       if(IS_REAL_CLIENT(player) && !ctf_stalemate)
-                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
-               }
-
-               if(!player.wps_flagreturn)
-               {
-                       entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
-                       owp.colormod = '0 0.8 0.8';
-                       //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
-                       setcefc(owp, ctf_Return_Customize);
-               }
-       }
-}
-
-void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
-{
-       float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
-       float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
-       float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
-       //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
-
-       vector targpos;
-       if(current_height) // make sure we can actually do this arcing path
-       {
-               targpos = (to + ('0 0 1' * current_height));
-               WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
-               if(trace_fraction < 1)
-               {
-                       //print("normal arc line failed, trying to find new pos...");
-                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
-                       targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
-                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
-                       if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
-                       /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
-               }
-       }
-       else { targpos = to; }
-
-       //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
-
-       vector desired_direction = normalize(targpos - from);
-       if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
-       else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
-}
-
-bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
-{
-       if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
-       {
-               // directional tracing only
-               float spreadlimit;
-               makevectors(passer_angle);
-
-               // find the closest point on the enemy to the center of the attack
-               float h; // hypotenuse, which is the distance between attacker to head
-               float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
-
-               h = vlen(head_center - passer_center);
-               a = h * (normalize(head_center - passer_center) * v_forward);
-
-               vector nearest_on_line = (passer_center + a * v_forward);
-               float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
-
-               spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
-               spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
-
-               if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
-                       { return true; }
-               else
-                       { return false; }
-       }
-       else { return true; }
-}
-
-
-// =======================
-// CaptureShield Functions
-// =======================
-
-bool ctf_CaptureShield_CheckStatus(entity p)
-{
-       int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
-       int players_worseeq, players_total;
-
-       if(ctf_captureshield_max_ratio <= 0)
-               return false;
-
-       s  = GameRules_scoring_add(p, CTF_CAPS, 0);
-       s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
-       s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
-       s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
-
-       sr = ((s - s2) + (s3 + s4));
-
-       if(sr >= -ctf_captureshield_min_negscore)
-               return false;
-
-       players_total = players_worseeq = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               if(DIFF_TEAM(it, p))
-                       continue;
-               se  = GameRules_scoring_add(it, CTF_CAPS, 0);
-               se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
-               se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
-               se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
-
-               ser = ((se - se2) + (se3 + se4));
-
-               if(ser <= sr)
-                       ++players_worseeq;
-               ++players_total;
-       });
-
-       // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
-       // use this rule here
-
-       if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
-               return false;
-
-       return true;
-}
-
-void ctf_CaptureShield_Update(entity player, bool wanted_status)
-{
-       bool updated_status = ctf_CaptureShield_CheckStatus(player);
-       if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
-               player.ctf_captureshielded = updated_status;
-       }
-}
-
-bool ctf_CaptureShield_Customize(entity this, entity client)
-{
-       if(!client.ctf_captureshielded) { return false; }
-       if(CTF_SAMETEAM(this, client)) { return false; }
-
-       return true;
-}
-
-void ctf_CaptureShield_Touch(entity this, entity toucher)
-{
-       if(!toucher.ctf_captureshielded) { return; }
-       if(CTF_SAMETEAM(this, toucher)) { return; }
-
-       vector mymid = (this.absmin + this.absmax) * 0.5;
-       vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
-
-       Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
-       if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
-}
-
-void ctf_CaptureShield_Spawn(entity flag)
-{
-       entity shield = new(ctf_captureshield);
-
-       shield.enemy = flag;
-       shield.team = flag.team;
-       settouch(shield, ctf_CaptureShield_Touch);
-       setcefc(shield, ctf_CaptureShield_Customize);
-       shield.effects = EF_ADDITIVE;
-       set_movetype(shield, MOVETYPE_NOCLIP);
-       shield.solid = SOLID_TRIGGER;
-       shield.avelocity = '7 0 11';
-       shield.scale = 0.5;
-
-       setorigin(shield, flag.origin);
-       setmodel(shield, MDL_CTF_SHIELD);
-       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
-}
-
-
-// ====================
-// Drop/Pass/Throw Code
-// ====================
-
-void ctf_Handle_Drop(entity flag, entity player, int droptype)
-{
-       // declarations
-       player = (player ? player : flag.pass_sender);
-
-       // main
-       set_movetype(flag, MOVETYPE_TOSS);
-       flag.takedamage = DAMAGE_YES;
-       flag.angles = '0 0 0';
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
-       flag.ctf_droptime = time;
-       flag.ctf_dropper = player;
-       flag.ctf_status = FLAG_DROPPED;
-
-       // messages and sounds
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
-       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
-       ctf_EventLog("dropped", player.team, player);
-
-       // scoring
-       GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
-       GameRules_scoring_add(player, CTF_DROPS, 1);
-
-       // waypoints
-       if(autocvar_g_ctf_flag_dropped_waypoint) {
-               entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
-               wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
-       }
-
-       if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
-       {
-               WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
-               WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH));
-       }
-
-       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-
-       if(droptype == DROP_PASS)
-       {
-               flag.pass_distance = 0;
-               flag.pass_sender = NULL;
-               flag.pass_target = NULL;
-       }
-}
-
-void ctf_Handle_Retrieve(entity flag, entity player)
-{
-       entity sender = flag.pass_sender;
-
-       // transfer flag to player
-       flag.owner = player;
-       flag.owner.flagcarried = flag;
-       GameRules_scoring_vip(player, true);
-
-       // reset flag
-       if(player.vehicle)
-       {
-               setattachment(flag, player.vehicle, "");
-               setorigin(flag, VEHICLE_FLAG_OFFSET);
-               flag.scale = VEHICLE_FLAG_SCALE;
-       }
-       else
-       {
-               setattachment(flag, player, "");
-               setorigin(flag, FLAG_CARRY_OFFSET);
-       }
-       set_movetype(flag, MOVETYPE_NONE);
-       flag.takedamage = DAMAGE_NO;
-       flag.solid = SOLID_NOT;
-       flag.angles = '0 0 0';
-       flag.ctf_status = FLAG_CARRY;
-
-       // messages and sounds
-       _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
-       ctf_EventLog("receive", flag.team, player);
-
-       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
-               if(it == sender)
-                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
-               else if(it == player)
-                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
-               else if(SAME_TEAM(it, sender))
-                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
-       });
-
-       // create new waypoint
-       ctf_FlagcarrierWaypoints(player);
-
-       sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
-       player.throw_antispam = sender.throw_antispam;
-
-       flag.pass_distance = 0;
-       flag.pass_sender = NULL;
-       flag.pass_target = NULL;
-}
-
-void ctf_Handle_Throw(entity player, entity receiver, int droptype)
-{
-       entity flag = player.flagcarried;
-       vector targ_origin, flag_velocity;
-
-       if(!flag) { return; }
-       if((droptype == DROP_PASS) && !receiver) { return; }
-
-       if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
-
-       // reset the flag
-       setattachment(flag, NULL, "");
-       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
-       flag.owner.flagcarried = NULL;
-       GameRules_scoring_vip(flag.owner, false);
-       flag.owner = NULL;
-       flag.solid = SOLID_TRIGGER;
-       flag.ctf_dropper = player;
-       flag.ctf_droptime = time;
-       navigation_dynamicgoal_set(flag);
-
-       flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
-
-       switch(droptype)
-       {
-               case DROP_PASS:
-               {
-                       // warpzone support:
-                       // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
-                       // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
-                       WarpZone_RefSys_Copy(flag, receiver);
-                       WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
-                       targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
-
-                       flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' *  player.origin.x) + ('0 1 0' *  player.origin.y))); // for the sake of this check, exclude Z axis
-                       ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
-
-                       // main
-                       set_movetype(flag, MOVETYPE_FLY);
-                       flag.takedamage = DAMAGE_NO;
-                       flag.pass_sender = player;
-                       flag.pass_target = receiver;
-                       flag.ctf_status = FLAG_PASSING;
-
-                       // other
-                       _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
-                       WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
-                       ctf_EventLog("pass", flag.team, player);
-                       break;
-               }
-
-               case DROP_THROW:
-               {
-                       makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
-
-                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
-                       flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
-                       ctf_Handle_Drop(flag, player, droptype);
-                       break;
-               }
-
-               case DROP_RESET:
-               {
-                       flag.velocity = '0 0 0'; // do nothing
-                       break;
-               }
-
-               default:
-               case DROP_NORMAL:
-               {
-                       flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
-                       ctf_Handle_Drop(flag, player, droptype);
-                       break;
-               }
-       }
-
-       // kill old waypointsprite
-       WaypointSprite_Ping(player.wps_flagcarrier);
-       WaypointSprite_Kill(player.wps_flagcarrier);
-
-       if(player.wps_enemyflagcarrier)
-               WaypointSprite_Kill(player.wps_enemyflagcarrier);
-
-       if(player.wps_flagreturn)
-               WaypointSprite_Kill(player.wps_flagreturn);
-
-       // captureshield
-       ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
-}
-
-void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
-{
-       return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
-}
-
-// ==============
-// Event Handlers
-// ==============
-
-void nades_GiveBonus(entity player, float score);
-
-void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
-{
-       entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
-       entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
-       entity player_team_flag = NULL, tmp_entity;
-       float old_time, new_time;
-
-       if(!player) { return; } // without someone to give the reward to, we can't possibly cap
-       if(CTF_DIFFTEAM(player, flag)) { return; }
-       if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
-
-       if (toucher.goalentity == flag.bot_basewaypoint)
-               toucher.goalentity_lock_timeout = 0;
-
-       if(ctf_oneflag)
-       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-       if(SAME_TEAM(tmp_entity, player))
-       {
-               player_team_flag = tmp_entity;
-               break;
-       }
-
-       nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
-
-       player.throw_prevtime = time;
-       player.throw_count = 0;
-
-       // messages and sounds
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
-       ctf_CaptureRecord(enemy_flag, player);
-       _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
-
-       switch(capturetype)
-       {
-               case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
-               case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
-               default: break;
-       }
-
-       // scoring
-       float pscore = 0;
-       if(enemy_flag.score_capture || flag.score_capture)
-               pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
-       GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
-       float capscore = 0;
-       if(enemy_flag.score_team_capture || flag.score_team_capture)
-               capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
-       GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
-
-       old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
-       new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
-       if(!old_time || new_time < old_time)
-               GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
-
-       // effects
-       Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
-       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
-
-       // other
-       if(capturetype == CAPTURE_NORMAL)
-       {
-               WaypointSprite_Kill(player.wps_flagcarrier);
-               if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
-
-               if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
-                       { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
-       }
-
-       flag.enemy = toucher;
-
-       // reset the flag
-       player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
-       ctf_RespawnFlag(enemy_flag);
-}
-
-void ctf_Handle_Return(entity flag, entity player)
-{
-       // messages and sounds
-       if(IS_MONSTER(player))
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
-       }
-       else if(flag.team)
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
-       }
-       _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
-       ctf_EventLog("return", flag.team, player);
-
-       // scoring
-       if(IS_PLAYER(player))
-       {
-               GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
-               GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
-
-               nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
-       }
-
-       TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
-
-       if(flag.ctf_dropper)
-       {
-               GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
-               ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
-               flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
-       }
-
-       // other
-       if(player.flagcarried == flag)
-               WaypointSprite_Kill(player.wps_flagcarrier);
-
-       flag.enemy = player;
-
-       // reset the flag
-       ctf_RespawnFlag(flag);
-}
-
-void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
-{
-       // declarations
-       float pickup_dropped_score; // used to calculate dropped pickup score
-
-       // attach the flag to the player
-       flag.owner = player;
-       player.flagcarried = flag;
-       GameRules_scoring_vip(player, true);
-       if(player.vehicle)
-       {
-               setattachment(flag, player.vehicle, "");
-               setorigin(flag, VEHICLE_FLAG_OFFSET);
-               flag.scale = VEHICLE_FLAG_SCALE;
-       }
-       else
-       {
-               setattachment(flag, player, "");
-               setorigin(flag, FLAG_CARRY_OFFSET);
-       }
-
-       // flag setup
-       set_movetype(flag, MOVETYPE_NONE);
-       flag.takedamage = DAMAGE_NO;
-       flag.solid = SOLID_NOT;
-       flag.angles = '0 0 0';
-       flag.ctf_status = FLAG_CARRY;
-
-       switch(pickuptype)
-       {
-               case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
-               case PICKUP_DROPPED: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
-               default: break;
-       }
-
-       // messages and sounds
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
-       if(ctf_stalemate)
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
-       if(!flag.team)
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
-       else if(CTF_DIFFTEAM(player, flag))
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
-       else
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
-
-       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-
-       if(!flag.team)
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
-
-       if(flag.team)
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
-                       if(CTF_SAMETEAM(flag, it))
-                       if(SAME_TEAM(player, it))
-                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-                       else
-                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
-               });
-
-       _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
-
-       // scoring
-       GameRules_scoring_add(player, CTF_PICKUPS, 1);
-       nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
-       switch(pickuptype)
-       {
-               case PICKUP_BASE:
-               {
-                       GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
-                       ctf_EventLog("steal", flag.team, player);
-                       break;
-               }
-
-               case PICKUP_DROPPED:
-               {
-                       pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
-                       pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
-                       LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
-                       GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
-                       ctf_EventLog("pickup", flag.team, player);
-                       break;
-               }
-
-               default: break;
-       }
-
-       // speedrunning
-       if(pickuptype == PICKUP_BASE)
-       {
-               flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
-               if((player.speedrunning) && (ctf_captimerecord))
-                       ctf_FakeTimeLimit(player, time + ctf_captimerecord);
-       }
-
-       // effects
-       Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
-
-       // waypoints
-       if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
-       ctf_FlagcarrierWaypoints(player);
-       WaypointSprite_Ping(player.wps_flagcarrier);
-}
-
-
-// ===================
-// Main Flag Functions
-// ===================
-
-void ctf_CheckFlagReturn(entity flag, int returntype)
-{
-       if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
-       {
-               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH)); }
-
-               if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
-               {
-                       switch(returntype)
-                       {
-                               case RETURN_DROPPED:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
-                               case RETURN_DAMAGE:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
-                               case RETURN_SPEEDRUN:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
-                               case RETURN_NEEDKILL:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
-                               default:
-                               case RETURN_TIMEOUT:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
-                       }
-                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
-                       ctf_EventLog("returned", flag.team, NULL);
-                       flag.enemy = NULL;
-                       ctf_RespawnFlag(flag);
-               }
-       }
-}
-
-bool ctf_Stalemate_Customize(entity this, entity client)
-{
-       // make spectators see what the player would see
-       entity e = WaypointSprite_getviewentity(client);
-       entity wp_owner = this.owner;
-
-       // team waypoints
-       //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
-       if(SAME_TEAM(wp_owner, e)) { return false; }
-       if(!IS_PLAYER(e)) { return false; }
-
-       return true;
-}
-
-void ctf_CheckStalemate()
-{
-       // declarations
-       int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
-       entity tmp_entity;
-
-       entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
-
-       // build list of stale flags
-       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-       {
-               if(autocvar_g_ctf_stalemate)
-               if(tmp_entity.ctf_status != FLAG_BASE)
-               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
-               {
-                       tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
-                       ctf_staleflaglist = tmp_entity;
-
-                       switch(tmp_entity.team)
-                       {
-                               case NUM_TEAM_1: ++stale_red_flags; break;
-                               case NUM_TEAM_2: ++stale_blue_flags; break;
-                               case NUM_TEAM_3: ++stale_yellow_flags; break;
-                               case NUM_TEAM_4: ++stale_pink_flags; break;
-                               default: ++stale_neutral_flags; break;
-                       }
-               }
-       }
-
-       if(ctf_oneflag)
-               stale_flags = (stale_neutral_flags >= 1);
-       else
-               stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
-
-       if(ctf_oneflag && stale_flags == 1)
-               ctf_stalemate = true;
-       else if(stale_flags >= 2)
-               ctf_stalemate = true;
-       else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
-               { ctf_stalemate = false; wpforenemy_announced = false; }
-       else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
-               { ctf_stalemate = false; wpforenemy_announced = false; }
-
-       // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
-       if(ctf_stalemate)
-       {
-               for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
-               {
-                       if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
-                       {
-                               entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
-                               wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
-                               setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
-                       }
-               }
-
-               if (!wpforenemy_announced)
-               {
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
-
-                       wpforenemy_announced = true;
-               }
-       }
-}
-
-void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               if(autocvar_g_ctf_flag_return_damage_delay)
-                       this.ctf_flagdamaged_byworld = true;
-               else
-               {
-                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
-                       ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
-               }
-               return;
-       }
-       if(autocvar_g_ctf_flag_return_damage)
-       {
-               // reduce health and check if it should be returned
-               TakeResource(this, RESOURCE_HEALTH, damage);
-               ctf_CheckFlagReturn(this, RETURN_DAMAGE);
-               return;
-       }
-}
-
-void ctf_FlagThink(entity this)
-{
-       // declarations
-       entity tmp_entity;
-
-       this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
-
-       // captureshield
-       if(this == ctf_worldflaglist) // only for the first flag
-               FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
-
-       // sanity checks
-       if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
-               LOG_TRACE("wtf the flag got squashed?");
-               tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
-               if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
-                       setsize(this, this.m_mins, this.m_maxs);
-       }
-
-       // main think method
-       switch(this.ctf_status)
-       {
-               case FLAG_BASE:
-               {
-                       if(autocvar_g_ctf_dropped_capture_radius)
-                       {
-                               for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-                                       if(tmp_entity.ctf_status == FLAG_DROPPED)
-                                       if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
-                                       if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
-                                               ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
-                       }
-                       return;
-               }
-
-               case FLAG_DROPPED:
-               {
-                       this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
-
-                       if(autocvar_g_ctf_flag_dropped_floatinwater)
-                       {
-                               vector midpoint = ((this.absmin + this.absmax) * 0.5);
-                               if(pointcontents(midpoint) == CONTENT_WATER)
-                               {
-                                       this.velocity = this.velocity * 0.5;
-
-                                       if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
-                                               { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
-                                       else
-                                               { set_movetype(this, MOVETYPE_FLY); }
-                               }
-                               else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
-                       }
-                       if(autocvar_g_ctf_flag_return_dropped)
-                       {
-                               if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
-                               {
-                                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
-                                       ctf_CheckFlagReturn(this, RETURN_DROPPED);
-                                       return;
-                               }
-                       }
-                       if(this.ctf_flagdamaged_byworld)
-                       {
-                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
-                               ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
-                               return;
-                       }
-                       else if(autocvar_g_ctf_flag_return_time)
-                       {
-                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
-                               ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
-                               return;
-                       }
-                       return;
-               }
-
-               case FLAG_CARRY:
-               {
-                       if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
-                       {
-                               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
-                               ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
-
-                               CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
-                               ImpulseCommands(this.owner);
-                       }
-                       if(autocvar_g_ctf_stalemate)
-                       {
-                               if(time >= wpforenemy_nextthink)
-                               {
-                                       ctf_CheckStalemate();
-                                       wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
-                               }
-                       }
-                       if(CTF_SAMETEAM(this, this.owner) && this.team)
-                       {
-                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
-                                       ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
-                               else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
-                                       ctf_Handle_Return(this, this.owner);
-                       }
-                       return;
-               }
-
-               case FLAG_PASSING:
-               {
-                       vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
-                       targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
-                       WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
-
-                       if((this.pass_target == NULL)
-                               || (IS_DEAD(this.pass_target))
-                               || (this.pass_target.flagcarried)
-                               || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
-                               || ((trace_fraction < 1) && (trace_ent != this.pass_target))
-                               || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
-                       {
-                               // give up, pass failed
-                               ctf_Handle_Drop(this, NULL, DROP_PASS);
-                       }
-                       else
-                       {
-                               // still a viable target, go for it
-                               ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
-                       }
-                       return;
-               }
-
-               default: // this should never happen
-               {
-                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
-                       return;
-               }
-       }
-}
-
-METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
-{
-       return = false;
-       if(game_stopped) return;
-       if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
-
-       bool is_not_monster = (!IS_MONSTER(toucher));
-
-       // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
-       if(ITEM_TOUCH_NEEDKILL())
-       {
-               if(!autocvar_g_ctf_flag_return_damage_delay)
-               {
-                       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
-                       ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
-               }
-               if(!flag.ctf_flagdamaged_byworld) { return; }
-       }
-
-       // special touch behaviors
-       if(STAT(FROZEN, toucher)) { return; }
-       else if(IS_VEHICLE(toucher))
-       {
-               if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
-                       toucher = toucher.owner; // the player is actually the vehicle owner, not other
-               else
-                       return; // do nothing
-       }
-       else if(IS_MONSTER(toucher))
-       {
-               if(!autocvar_g_ctf_allow_monster_touch)
-                       return; // do nothing
-       }
-       else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
-       {
-               if(time > flag.wait) // if we haven't in a while, play a sound/effect
-               {
-                       Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
-                       _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
-                       flag.wait = time + FLAG_TOUCHRATE;
-               }
-               return;
-       }
-       else if(IS_DEAD(toucher)) { return; }
-
-       switch(flag.ctf_status)
-       {
-               case FLAG_BASE:
-               {
-                       if(ctf_oneflag)
-                       {
-                               if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
-                                       ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
-                               else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
-                                       ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
-                       }
-                       else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
-                               ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
-                       {
-                               ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
-                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
-                       }
-                       else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
-                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
-                       break;
-               }
-
-               case FLAG_DROPPED:
-               {
-                       if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
-                               ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
-                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
-                               ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
-                       break;
-               }
-
-               case FLAG_CARRY:
-               {
-                       LOG_TRACE("Someone touched a flag even though it was being carried?");
-                       break;
-               }
-
-               case FLAG_PASSING:
-               {
-                       if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
-                       {
-                               if(DIFF_TEAM(toucher, flag.pass_sender))
-                               {
-                                       if(ctf_Immediate_Return_Allowed(flag, toucher))
-                                               ctf_Handle_Return(flag, toucher);
-                                       else if(is_not_monster && (!toucher.flagcarried))
-                                               ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
-                               }
-                               else if(!toucher.flagcarried)
-                                       ctf_Handle_Retrieve(flag, toucher);
-                       }
-                       break;
-               }
-       }
-}
-
-.float last_respawn;
-void ctf_RespawnFlag(entity flag)
-{
-       // check for flag respawn being called twice in a row
-       if(flag.last_respawn > time - 0.5)
-               { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
-
-       flag.last_respawn = time;
-
-       // reset the player (if there is one)
-       if((flag.owner) && (flag.owner.flagcarried == flag))
-       {
-               WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
-               WaypointSprite_Kill(flag.owner.wps_flagreturn);
-               WaypointSprite_Kill(flag.wps_flagcarrier);
-
-               flag.owner.flagcarried = NULL;
-               GameRules_scoring_vip(flag.owner, false);
-
-               if(flag.speedrunning)
-                       ctf_FakeTimeLimit(flag.owner, -1);
-       }
-
-       if((flag.owner) && (flag.owner.vehicle))
-               flag.scale = FLAG_SCALE;
-
-       if(flag.ctf_status == FLAG_DROPPED)
-               { WaypointSprite_Kill(flag.wps_flagdropped); }
-
-       // reset the flag
-       setattachment(flag, NULL, "");
-       setorigin(flag, flag.ctf_spawnorigin);
-
-       set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
-       flag.takedamage = DAMAGE_NO;
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
-       flag.solid = SOLID_TRIGGER;
-       flag.velocity = '0 0 0';
-       flag.angles = flag.mangle;
-       flag.flags = FL_ITEM | FL_NOTARGET;
-
-       flag.ctf_status = FLAG_BASE;
-       flag.owner = NULL;
-       flag.pass_distance = 0;
-       flag.pass_sender = NULL;
-       flag.pass_target = NULL;
-       flag.ctf_dropper = NULL;
-       flag.ctf_pickuptime = 0;
-       flag.ctf_droptime = 0;
-       flag.ctf_flagdamaged_byworld = false;
-       navigation_dynamicgoal_unset(flag);
-
-       ctf_CheckStalemate();
-}
-
-void ctf_Reset(entity this)
-{
-       if(this.owner && IS_PLAYER(this.owner))
-               ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
-
-       this.enemy = NULL;
-       ctf_RespawnFlag(this);
-}
-
-bool ctf_FlagBase_Customize(entity this, entity client)
-{
-       entity e = WaypointSprite_getviewentity(client);
-       entity wp_owner = this.owner;
-       entity flag = e.flagcarried;
-       if(flag && CTF_SAMETEAM(e, flag))
-               return false;
-       if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
-               return false;
-       return true;
-}
-
-void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
-{
-       // bot waypoints
-       waypoint_spawnforitem_force(this, this.origin);
-       navigation_dynamicgoal_init(this, true);
-
-       // waypointsprites
-       entity basename;
-       switch (this.team)
-       {
-               case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
-               case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
-               case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
-               case NUM_TEAM_4: basename = WP_FlagBasePink; break;
-               default: basename = WP_FlagBaseNeutral; break;
-       }
-
-       entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
-       wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
-       WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
-       setcefc(wp, ctf_FlagBase_Customize);
-
-       // captureshield setup
-       ctf_CaptureShield_Spawn(this);
-}
-
-.bool pushable;
-
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
-{
-       // main setup
-       flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
-       ctf_worldflaglist = flag;
-
-       setattachment(flag, NULL, "");
-
-       flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
-       flag.team = teamnumber;
-       flag.classname = "item_flag_team";
-       flag.target = "###item###"; // wut?
-       flag.flags = FL_ITEM | FL_NOTARGET;
-       IL_PUSH(g_items, flag);
-       flag.solid = SOLID_TRIGGER;
-       flag.takedamage = DAMAGE_NO;
-       flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
-       flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
-       flag.event_damage = ctf_FlagDamage;
-       flag.pushable = true;
-       flag.teleportable = TELEPORT_NORMAL;
-       flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
-       flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
-       flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
-       if(flag.damagedbycontents)
-               IL_PUSH(g_damagedbycontents, flag);
-       flag.velocity = '0 0 0';
-       flag.mangle = flag.angles;
-       flag.reset = ctf_Reset;
-       settouch(flag, ctf_FlagTouch);
-       setthink(flag, ctf_FlagThink);
-       flag.nextthink = time + FLAG_THINKRATE;
-       flag.ctf_status = FLAG_BASE;
-
-       // crudely force them all to 0
-       if(autocvar_g_ctf_score_ignore_fields)
-               flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
-
-       string teamname = Static_Team_ColorName_Lower(teamnumber);
-       // appearence
-       if(!flag.scale)                         { flag.scale = FLAG_SCALE; }
-       if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
-       if(flag.model == "")            { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
-       if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
-       if (flag.passeffect == "")      { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
-       if (flag.capeffect == "")       { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
-
-       // sounds
-#define X(s,b) \
-               if(flag.s == "") flag.s = b; \
-               precache_sound(flag.s);
-
-       X(snd_flag_taken,               strzone(SND(CTF_TAKEN(teamnumber))))
-       X(snd_flag_returned,    strzone(SND(CTF_RETURNED(teamnumber))))
-       X(snd_flag_capture,     strzone(SND(CTF_CAPTURE(teamnumber))))
-       X(snd_flag_dropped,     strzone(SND(CTF_DROPPED(teamnumber))))
-       X(snd_flag_respawn,     strzone(SND(CTF_RESPAWN)))
-       X(snd_flag_touch,               strzone(SND(CTF_TOUCH)))
-       X(snd_flag_pass,                strzone(SND(CTF_PASS)))
-#undef X
-
-       // precache
-       precache_model(flag.model);
-
-       // appearence
-       _setmodel(flag, flag.model); // precision set below
-       setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
-       flag.m_mins = flag.mins; // store these for squash checks
-       flag.m_maxs = flag.maxs;
-       setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
-
-       if(autocvar_g_ctf_flag_glowtrails)
-       {
-               switch(teamnumber)
-               {
-                       case NUM_TEAM_1: flag.glow_color = 251; break;
-                       case NUM_TEAM_2: flag.glow_color = 210; break;
-                       case NUM_TEAM_3: flag.glow_color = 110; break;
-                       case NUM_TEAM_4: flag.glow_color = 145; break;
-                       default:                 flag.glow_color = 254; break;
-               }
-               flag.glow_size = 25;
-               flag.glow_trail = 1;
-       }
-
-       flag.effects |= EF_LOWPRECISION;
-       if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
-       if(autocvar_g_ctf_dynamiclights)
-       {
-               switch(teamnumber)
-               {
-                       case NUM_TEAM_1: flag.effects |= EF_RED; break;
-                       case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
-                       case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
-                       case NUM_TEAM_4: flag.effects |= EF_RED; break;
-                       default:                 flag.effects |= EF_DIMLIGHT; break;
-               }
-       }
-
-       // flag placement
-       if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
-       {
-               flag.dropped_origin = flag.origin;
-               flag.noalign = true;
-               set_movetype(flag, MOVETYPE_NONE);
-       }
-       else // drop to floor, automatically find a platform and set that as spawn origin
-       {
-               flag.noalign = false;
-               droptofloor(flag);
-               set_movetype(flag, MOVETYPE_NONE);
-       }
-
-       InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-// NOTE: LEGACY CODE, needs to be re-written!
-
-void havocbot_ctf_calculate_middlepoint()
-{
-       entity f;
-       vector s = '0 0 0';
-       vector fo = '0 0 0';
-       int n = 0;
-
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               fo = f.origin;
-               s = s + fo;
-               f = f.ctf_worldflagnext;
-               n++;
-       }
-       if(!n)
-               return;
-
-       havocbot_middlepoint = s / n;
-       havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
-
-       havocbot_symmetry_axis_m = 0;
-       havocbot_symmetry_axis_q = 0;
-       if(n == 2)
-       {
-               // for symmetrical editing of waypoints
-               entity f1 = ctf_worldflaglist;
-               entity f2 = f1.ctf_worldflagnext;
-               float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
-               float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
-               havocbot_symmetry_axis_m = m;
-               havocbot_symmetry_axis_q = q;
-       }
-       havocbot_symmetry_origin_order = n;
-}
-
-
-entity havocbot_ctf_find_flag(entity bot)
-{
-       entity f;
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               if (CTF_SAMETEAM(bot, f))
-                       return f;
-               f = f.ctf_worldflagnext;
-       }
-       return NULL;
-}
-
-entity havocbot_ctf_find_enemy_flag(entity bot)
-{
-       entity f;
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               if(ctf_oneflag)
-               {
-                       if(CTF_DIFFTEAM(bot, f))
-                       {
-                               if(f.team)
-                               {
-                                       if(bot.flagcarried)
-                                               return f;
-                               }
-                               else if(!bot.flagcarried)
-                                       return f;
-                       }
-               }
-               else if (CTF_DIFFTEAM(bot, f))
-                       return f;
-               f = f.ctf_worldflagnext;
-       }
-       return NULL;
-}
-
-int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
-{
-       if (!teamplay)
-               return 0;
-
-       int c = 0;
-
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
-                       continue;
-
-               if(vdist(it.origin - org, <, tc_radius))
-                       ++c;
-       });
-
-       return c;
-}
-
-// unused
-#if 0
-void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (CTF_SAMETEAM(this, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(this, head, ratingscale, 10000);
-}
-#endif
-
-void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (CTF_SAMETEAM(this, head))
-               {
-                       if (this.flagcarried)
-                       if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
-                       {
-                               head = head.ctf_worldflagnext; // skip base if it has a different group
-                               continue;
-                       }
-                       break;
-               }
-               head = head.ctf_worldflagnext;
-       }
-       if (!head)
-               return;
-
-       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if(ctf_oneflag)
-               {
-                       if(CTF_DIFFTEAM(this, head))
-                       {
-                               if(head.team)
-                               {
-                                       if(this.flagcarried)
-                                               break;
-                               }
-                               else if(!this.flagcarried)
-                                       break;
-                       }
-               }
-               else if(CTF_DIFFTEAM(this, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(this, head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
-{
-       if (!bot_waypoints_for_items)
-       {
-               havocbot_goalrating_ctf_enemyflag(this, ratingscale);
-               return;
-       }
-
-       entity head;
-
-       head = havocbot_ctf_find_enemy_flag(this);
-
-       if (!head)
-               return;
-
-       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
-{
-       entity mf;
-
-       mf = havocbot_ctf_find_flag(this);
-
-       if(mf.ctf_status == FLAG_BASE)
-               return;
-
-       if(mf.tag_entity)
-               navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               // flag is out in the field
-               if(head.ctf_status != FLAG_BASE)
-               if(head.tag_entity==NULL)       // dropped
-               {
-                       if(df_radius)
-                       {
-                               if(vdist(org - head.origin, <, df_radius))
-                                       navigation_routerating(this, head, ratingscale, 10000);
-                       }
-                       else
-                               navigation_routerating(this, head, ratingscale, 10000);
-               }
-
-               head = head.ctf_worldflagnext;
-       }
-}
-
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
-       IL_EACH(g_items, it.bot_pickup,
-       {
-               // gather health and armor only
-               if (it.solid)
-               if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
-               if (vdist(it.origin - org, <, sradius))
-               {
-                       // get the value of the item
-                       float t = it.bot_pickupevalfunc(this, it) * 0.0001;
-                       if (t > 0)
-                               navigation_routerating(this, it, t * ratingscale, 500);
-               }
-       });
-}
-
-void havocbot_ctf_reset_role(entity this)
-{
-       float cdefense, cmiddle, coffense;
-       entity mf, ef;
-       float c;
-
-       if(IS_DEAD(this))
-               return;
-
-       // Check ctf flags
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(this);
-       ef = havocbot_ctf_find_enemy_flag(this);
-
-       // Retrieve stolen flag
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       // If enemy flag is taken go to the middle to intercept pursuers
-       if(ef.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
-
-       if(c==1)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
-               return;
-       }
-
-       // Evaluate best position to take
-       // Count mates on middle position
-       cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
-
-       // Count mates on defense position
-       cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
-
-       // Count mates on offense position
-       coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
-
-       if(cdefense<=coffense)
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
-       else if(coffense<=cmiddle)
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
-       else
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-}
-
-void havocbot_role_ctf_carrier(entity this)
-{
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried == NULL)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               if(ctf_oneflag)
-                       havocbot_goalrating_ctf_enemybase(this, 50000);
-               else
-                       havocbot_goalrating_ctf_ourbase(this, 50000);
-
-               if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
-                       havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-
-               entity head = ctf_worldflaglist;
-               while (head)
-               {
-                       if (this.goalentity == head.bot_basewaypoint)
-                       {
-                               this.goalentity_lock_timeout = time + 5;
-                               break;
-                       }
-                       head = head.ctf_worldflagnext;
-               }
-
-               if (this.goalentity)
-                       this.havocbot_cantfindflag = time + 10;
-               else if (time > this.havocbot_cantfindflag)
-               {
-                       // Can't navigate to my own base, suicide!
-                       // TODO: drop it and wander around
-                       Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
-                       return;
-               }
-       }
-}
-
-void havocbot_role_ctf_escort(entity this)
-{
-       entity mf, ef;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If enemy flag is back on the base switch to previous role
-       ef = havocbot_ctf_find_enemy_flag(this);
-       if(ef.ctf_status==FLAG_BASE)
-       {
-               this.havocbot_role = this.havocbot_previous_role;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // If the flag carrier reached the base switch to defense
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       if(vdist(ef.origin - mf.dropped_origin, <, 300))
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-       {
-               this.havocbot_role_timeout = time + random() * 30 + 60;
-       }
-
-       // If nothing happened just switch to previous role
-       if (time > this.havocbot_role_timeout)
-       {
-               this.havocbot_role = this.havocbot_previous_role;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // Chase the flag carrier
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_enemyflag(this, 30000);
-               havocbot_goalrating_ctf_ourstolenflag(this, 40000);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_offense(entity this)
-{
-       entity mf, ef;
-       vector pos;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // Check flags
-       mf = havocbot_ctf_find_flag(this);
-       ef = havocbot_ctf_find_enemy_flag(this);
-
-       // Own flag stolen
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               if(mf.tag_entity)
-                       pos = mf.tag_entity.origin;
-               else
-                       pos = mf.origin;
-
-               // Try to get it if closer than the enemy base
-               if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
-               {
-                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
-                       return;
-               }
-       }
-
-       // Escort flag carrier
-       if(ef.ctf_status!=FLAG_BASE)
-       {
-               if(ef.tag_entity)
-                       pos = ef.tag_entity.origin;
-               else
-                       pos = ef.origin;
-
-               if(vdist(pos - mf.dropped_origin, >, 700))
-               {
-                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
-                       return;
-               }
-       }
-
-       // About to fail, switch to middlefield
-       if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 120;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_enemybase(this, 20000);
-               havocbot_goalrating_items(this, 5000, this.origin, 1000);
-               havocbot_goalrating_items(this, 1000, this.origin, 10000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever(entity this)
-{
-       entity mf;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If flag is back on the base switch to previous role
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status==FLAG_BASE)
-       {
-               if (mf.enemy == this) // did this bot return the flag?
-                       navigation_goalrating_timeout_force(this);
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 20;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               float rt_radius;
-               rt_radius = 10000;
-
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
-               havocbot_goalrating_ctf_enemybase(this, 30000);
-               havocbot_goalrating_items(this, 500, this.origin, rt_radius);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_middle(entity this)
-{
-       entity mf;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 10;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               vector org;
-
-               org = havocbot_middlepoint;
-               org.z = this.origin.z;
-
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 2500, this.origin, 10000);
-               havocbot_goalrating_ctf_enemybase(this, 2500);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_defense(entity this)
-{
-       entity mf;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If own flag was captured
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 30;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-       if (navigation_goalrating_timeout(this))
-       {
-               vector org = mf.dropped_origin;
-
-               navigation_goalrating_start(this);
-
-               // if enemies are closer to our base, go there
-               entity closestplayer = NULL;
-               float distance, bestdistance = 10000;
-               FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
-                       distance = vlen(org - it.origin);
-                       if(distance<bestdistance)
-                       {
-                               closestplayer = it;
-                               bestdistance = distance;
-                       }
-               });
-
-               if(closestplayer)
-               if(DIFF_TEAM(closestplayer, this))
-               if(vdist(org - this.origin, >, 1000))
-               if(checkpvs(this.origin,closestplayer)||random()<0.5)
-                       havocbot_goalrating_ctf_ourbase(this, 30000);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 20000);
-               havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 5000, this.origin, 10000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_setrole(entity bot, int role)
-{
-       string s = "(null)";
-       switch(role)
-       {
-               case HAVOCBOT_CTF_ROLE_CARRIER:
-                       s = "carrier";
-                       bot.havocbot_role = havocbot_role_ctf_carrier;
-                       bot.havocbot_role_timeout = 0;
-                       bot.havocbot_cantfindflag = time + 10;
-                       if (bot.havocbot_previous_role != bot.havocbot_role)
-                               navigation_goalrating_timeout_force(bot);
-                       break;
-               case HAVOCBOT_CTF_ROLE_DEFENSE:
-                       s = "defense";
-                       bot.havocbot_role = havocbot_role_ctf_defense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_MIDDLE:
-                       s = "middle";
-                       bot.havocbot_role = havocbot_role_ctf_middle;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_OFFENSE:
-                       s = "offense";
-                       bot.havocbot_role = havocbot_role_ctf_offense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_RETRIEVER:
-                       s = "retriever";
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_retriever;
-                       bot.havocbot_role_timeout = time + 10;
-                       if (bot.havocbot_previous_role != bot.havocbot_role)
-                               navigation_goalrating_timeout_expire(bot, 2);
-                       break;
-               case HAVOCBOT_CTF_ROLE_ESCORT:
-                       s = "escort";
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_escort;
-                       bot.havocbot_role_timeout = time + 30;
-                       if (bot.havocbot_previous_role != bot.havocbot_role)
-                               navigation_goalrating_timeout_expire(bot, 2);
-                       break;
-       }
-       LOG_TRACE(bot.netname, " switched to ", s);
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       int t = 0, t2 = 0, t3 = 0;
-       bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
-
-       // initially clear items so they can be set as necessary later.
-       STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING         | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
-                                                  | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
-                                                  | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
-                                                  | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
-                                                  | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
-                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
-
-       // scan through all the flags and notify the client about them
-       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING;              t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING;             t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING;   t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING;             t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
-               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
-
-               switch(flag.ctf_status)
-               {
-                       case FLAG_PASSING:
-                       case FLAG_CARRY:
-                       {
-                               if((flag.owner == player) || (flag.pass_sender == player))
-                                       STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
-                               else
-                                       STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
-                               break;
-                       }
-                       case FLAG_DROPPED:
-                       {
-                               STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
-                               break;
-                       }
-               }
-       }
-
-       // item for stopping players from capturing the flag too often
-       if(player.ctf_captureshielded)
-               STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
-
-       if(ctf_stalemate)
-               STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
-
-       // update the health of the flag carrier waypointsprite
-       if(player.wps_flagcarrier)
-               WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-}
-
-MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(4, float);
-       vector frag_force = M_ARGV(6, vector);
-
-       if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
-       {
-               if(frag_target == frag_attacker) // damage done to yourself
-               {
-                       frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
-                       frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
-               }
-               else // damage done to everyone else
-               {
-                       frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
-                       frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
-               }
-
-               M_ARGV(4, float) = frag_damage;
-               M_ARGV(6, vector) = frag_force;
-       }
-       else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
-       {
-               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
-               if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
-               {
-                       frag_target.wps_helpme_time = time;
-                       WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
-               }
-               // todo: add notification for when flag carrier needs help?
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
-       {
-               GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
-               GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
-       }
-
-       if(frag_target.flagcarried)
-       {
-               entity tmp_entity = frag_target.flagcarried;
-               ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
-               tmp_entity.ctf_dropper = NULL;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
-{
-       M_ARGV(2, float) = 0; // frag score
-       return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
-}
-
-void ctf_RemovePlayer(entity player)
-{
-       if(player.flagcarried)
-               { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
-
-       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               if(flag.pass_sender == player) { flag.pass_sender = NULL; }
-               if(flag.pass_target == player) { flag.pass_target = NULL; }
-               if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
-{
-       if(!autocvar_g_ctf_leaderboard)
-               return;
-
-       entity player = M_ARGV(0, entity);
-
-       if(IS_REAL_CLIENT(player))
-       {
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (int i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
-{
-       if(!autocvar_g_ctf_leaderboard)
-               return;
-
-       entity player = M_ARGV(0, entity);
-
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.flagcarried)
-       if(!autocvar_g_ctf_portalteleport)
-               { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
-{
-       if(MUTATOR_RETURNVALUE || game_stopped) return;
-
-       entity player = M_ARGV(0, entity);
-
-       if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
-       {
-               // pass the flag to a team mate
-               if(autocvar_g_ctf_pass)
-               {
-                       entity head, closest_target = NULL;
-                       head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
-
-                       while(head) // find the closest acceptable target to pass to
-                       {
-                               if(IS_PLAYER(head) && !IS_DEAD(head))
-                               if(head != player && SAME_TEAM(head, player))
-                               if(!head.speedrunning && !head.vehicle)
-                               {
-                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
-                                       vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
-                                       vector passer_center = CENTER_OR_VIEWOFS(player);
-
-                                       if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
-                                       {
-                                               if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
-                                               {
-                                                       if(IS_BOT_CLIENT(head))
-                                                       {
-                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
-                                                               ctf_Handle_Throw(head, player, DROP_PASS);
-                                                       }
-                                                       else
-                                                       {
-                                                               Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
-                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
-                                                       }
-                                                       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-                                                       return true;
-                                               }
-                                               else if(player.flagcarried && !head.flagcarried)
-                                               {
-                                                       if(closest_target)
-                                                       {
-                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
-                                                               if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
-                                                                       { closest_target = head; }
-                                                       }
-                                                       else { closest_target = head; }
-                                               }
-                                       }
-                               }
-                               head = head.chain;
-                       }
-
-                       if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
-               }
-
-               // throw the flag in front of you
-               if(autocvar_g_ctf_throw && player.flagcarried)
-               {
-                       if(player.throw_count == -1)
-                       {
-                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
-                               {
-                                       player.throw_prevtime = time;
-                                       player.throw_count = 1;
-                                       ctf_Handle_Throw(player, NULL, DROP_THROW);
-                                       return true;
-                               }
-                               else
-                               {
-                                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
-                                       return false;
-                               }
-                       }
-                       else
-                       {
-                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
-                               else { player.throw_count += 1; }
-                               if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
-
-                               player.throw_prevtime = time;
-                               ctf_Handle_Throw(player, NULL, DROP_THROW);
-                               return true;
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
-       {
-               player.wps_helpme_time = time;
-               WaypointSprite_HelpMePing(player.wps_flagcarrier);
-       }
-       else // create a normal help me waypointsprite
-       {
-               WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
-               WaypointSprite_Ping(player.wps_helpme);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
-{
-       entity player = M_ARGV(0, entity);
-       entity veh = M_ARGV(1, entity);
-
-       if(player.flagcarried)
-       {
-               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
-               {
-                       ctf_Handle_Throw(player, NULL, DROP_NORMAL);
-               }
-               else
-               {
-                       player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
-                       setattachment(player.flagcarried, veh, "");
-                       setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
-                       player.flagcarried.scale = VEHICLE_FLAG_SCALE;
-                       //player.flagcarried.angles = '0 0 0';
-               }
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.flagcarried)
-       {
-               setattachment(player.flagcarried, player, "");
-               setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
-               player.flagcarried.scale = FLAG_SCALE;
-               player.flagcarried.angles = '0 0 0';
-               player.flagcarried.nodrawtoclient = NULL;
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.flagcarried)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
-               ctf_RespawnFlag(player.flagcarried);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
-{
-       entity flag; // temporary entity for the search method
-
-       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               switch(flag.ctf_status)
-               {
-                       case FLAG_DROPPED:
-                       case FLAG_PASSING:
-                       {
-                               // lock the flag, game is over
-                               set_movetype(flag, MOVETYPE_NONE);
-                               flag.takedamage = DAMAGE_NO;
-                               flag.solid = SOLID_NOT;
-                               flag.nextthink = false; // stop thinking
-
-                               //dprint("stopping the ", flag.netname, " from moving.\n");
-                               break;
-                       }
-
-                       default:
-                       case FLAG_BASE:
-                       case FLAG_CARRY:
-                       {
-                               // do nothing for these flags
-                               break;
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       havocbot_ctf_reset_role(bot);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
-{
-       //M_ARGV(0, float) = ctf_teams;
-       M_ARGV(1, string) = "ctf_team";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{
-       entity spectatee = M_ARGV(0, entity);
-       entity client = M_ARGV(1, entity);
-
-       STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetRecords)
-{
-       int record_page = M_ARGV(0, int);
-       string ret_string = M_ARGV(1, string);
-
-       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
-       {
-               if (MapInfo_Get_ByID(i))
-               {
-                       float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
-
-                       if(!r)
-                               continue;
-
-                       // TODO: uid2name
-                       string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
-                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
-               }
-       }
-
-       M_ARGV(1, string) = ret_string;
-}
-
-bool superspec_Spectate(entity this, entity targ); // TODO
-void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
-MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
-{
-       entity player = M_ARGV(0, entity);
-       string cmd_name = M_ARGV(1, string);
-       int cmd_argc = M_ARGV(2, int);
-
-       if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
-
-       if(cmd_name == "followfc")
-       {
-               if(!g_ctf)
-                       return true;
-
-               int _team = 0;
-               bool found = false;
-
-               if(cmd_argc == 2)
-               {
-                       switch(argv(1))
-                       {
-                               case "red":    if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
-                               case "blue":   if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
-                               case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
-                               case "pink":   if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
-                       }
-               }
-
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(it.flagcarried && (it.team == _team || _team == 0))
-                       {
-                               found = true;
-                               if(_team == 0 && IS_SPEC(player) && player.enemy == it)
-                                       continue; // already spectating this fc, try another
-                               return superspec_Spectate(player, it);
-                       }
-               });
-
-               if(!found)
-                       superspec_msg("", "", player, "No active flag carrier\n", 1);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
-{
-       entity frag_target = M_ARGV(0, entity);
-
-       if(frag_target.flagcarried)
-               ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
-}
-
-
-// ==========
-// Spawnfuncs
-// ==========
-
-/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team one (Red).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red and blue as skins 0 and 1...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team1)
-{
-       if(!g_ctf) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_1, this);
-}
-
-/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team two (Blue).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red and blue as skins 0 and 1...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team2)
-{
-       if(!g_ctf) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_2, this);
-}
-
-/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team three (Yellow).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team3)
-{
-       if(!g_ctf) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_3, this);
-}
-
-/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team four (Pink).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team4)
-{
-       if(!g_ctf) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_4, this);
-}
-
-/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag (Neutral).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_neutral)
-{
-       if(!g_ctf) { delete(this); return; }
-       if(!cvar("g_ctf_oneflag")) { delete(this); return; }
-
-       ctf_FlagSetup(0, this);
-}
-
-/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
-Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
-Keys:
-"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
-"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
-spawnfunc(ctf_team)
-{
-       if(!g_ctf) { delete(this); return; }
-
-       this.classname = "ctf_team";
-       this.team = this.cnt + 1;
-}
-
-// compatibility for quake maps
-spawnfunc(team_CTF_redflag)    { spawnfunc_item_flag_team1(this);    }
-spawnfunc(team_CTF_blueflag)   { spawnfunc_item_flag_team2(this);    }
-spawnfunc(info_player_team1);
-spawnfunc(team_CTF_redplayer)  { spawnfunc_info_player_team1(this);  }
-spawnfunc(team_CTF_redspawn)   { spawnfunc_info_player_team1(this);  }
-spawnfunc(info_player_team2);
-spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this);  }
-spawnfunc(team_CTF_bluespawn)  { spawnfunc_info_player_team2(this);  }
-
-spawnfunc(team_CTF_neutralflag)        { spawnfunc_item_flag_neutral(this);  }
-spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this);  }
-
-// compatibility for wop maps
-spawnfunc(team_redplayer)      { spawnfunc_info_player_team1(this);  }
-spawnfunc(team_blueplayer)     { spawnfunc_info_player_team2(this);  }
-spawnfunc(team_ctl_redlolly)   { spawnfunc_item_flag_team1(this);    }
-spawnfunc(team_CTL_redlolly)   { spawnfunc_item_flag_team1(this);    }
-spawnfunc(team_ctl_bluelolly)  { spawnfunc_item_flag_team2(this);    }
-spawnfunc(team_CTL_bluelolly)  { spawnfunc_item_flag_team2(this);    }
-
-
-// ==============
-// Initialization
-// ==============
-
-// scoreboard setup
-void ctf_ScoreRules(int teams)
-{
-       CheckAllowedTeams(NULL);
-       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
-        field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
-        field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
-        field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
-        field(SP_CTF_PICKUPS, "pickups", 0);
-        field(SP_CTF_FCKILLS, "fckills", 0);
-        field(SP_CTF_RETURNS, "returns", 0);
-        field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
-       });
-}
-
-// code from here on is just to support maps that don't have flag and team entities
-void ctf_SpawnTeam (string teamname, int teamcolor)
-{
-       entity this = new_pure(ctf_team);
-       this.netname = teamname;
-       this.cnt = teamcolor - 1;
-       this.spawnfunc_checked = true;
-       this.team = teamcolor;
-}
-
-void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
-       ctf_teams = 0;
-
-       entity tmp_entity;
-       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-       {
-               //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
-               //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
-
-               switch(tmp_entity.team)
-               {
-                       case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
-                       case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
-                       case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
-                       case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
-               }
-               if(tmp_entity.team == 0) { ctf_oneflag = true; }
-       }
-
-       havocbot_ctf_calculate_middlepoint();
-
-       if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
-       {
-               ctf_teams = 0; // so set the default red and blue teams
-               BITSET_ASSIGN(ctf_teams, BIT(0));
-               BITSET_ASSIGN(ctf_teams, BIT(1));
-       }
-
-       //ctf_teams = bound(2, ctf_teams, 4);
-
-       // if no teams are found, spawn defaults
-       if(find(NULL, classname, "ctf_team") == NULL)
-       {
-               LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
-               if(ctf_teams & BIT(0))
-                       ctf_SpawnTeam("Red", NUM_TEAM_1);
-               if(ctf_teams & BIT(1))
-                       ctf_SpawnTeam("Blue", NUM_TEAM_2);
-               if(ctf_teams & BIT(2))
-                       ctf_SpawnTeam("Yellow", NUM_TEAM_3);
-               if(ctf_teams & BIT(3))
-                       ctf_SpawnTeam("Pink", NUM_TEAM_4);
-       }
-
-       ctf_ScoreRules(ctf_teams);
-}
-
-void ctf_Initialize()
-{
-       ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
-
-       ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
-       ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
-       ctf_captureshield_force = autocvar_g_ctf_shield_force;
-
-       InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
-}
-#endif
index 2f9643b4a411df5af8e3b3b86e33198913f0c24b..3cbd334b276544ea8977bff8fc654414c3146f01 100644 (file)
@@ -1,174 +1,5 @@
 #pragma once
 
-#ifdef SVQC
-
-void ctf_Initialize();
-
-REGISTER_MUTATOR(ctf, false)
-{
-    MUTATOR_STATIC();
-    MUTATOR_ONADD
-    {
-        GameRules_teams(true);
-        GameRules_limit_score(autocvar_capturelimit_override);
-        GameRules_limit_lead(autocvar_captureleadlimit_override);
-
-        ctf_Initialize();
-    }
-    return 0;
-}
-
-// used in cheats.qc
-void ctf_RespawnFlag(entity flag);
-
-// score rule declarations
-const int ST_CTF_CAPS = 1;
-
-CLASS(Flag, Pickup)
-    ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
-    ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
-ENDCLASS(Flag)
-Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
-void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
-
-// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
-
-const float FLAG_SCALE = 0.6;
-
-const float FLAG_THINKRATE = 0.2;
-const float FLAG_TOUCHRATE = 0.5;
-const float WPFE_THINKRATE = 0.5;
-
-const vector FLAG_DROP_OFFSET = ('0 0 32');
-const vector FLAG_CARRY_OFFSET = ('-16 0 8');
-#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
-const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
-const vector FLAG_FLOAT_OFFSET = ('0 0 32');
-const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
-
-const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
-const float VEHICLE_FLAG_SCALE = 1.0;
-
-// waypoint colors
-#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
-#define WPCOLOR_FLAGCARRIER(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
-//#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
-#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
-
-// sounds
-#define snd_flag_taken noise
-#define snd_flag_returned noise1
-#define snd_flag_capture noise2
-#define snd_flag_respawn noise3
-.string snd_flag_dropped;
-.string snd_flag_touch;
-.string snd_flag_pass;
-
-// score fields
-.float score_assist;
-.float score_capture;
-.float score_drop; // note: negated
-.float score_pickup;
-.float score_return;
-.float score_team_capture; // shouldn't be too high
-
-// effects
-.string toucheffect;
-.string passeffect;
-.string capeffect;
-
-// list of flags on the map
-entity ctf_worldflaglist;
-.entity ctf_worldflagnext;
-.entity ctf_staleflagnext;
-
-// waypoint sprites
-.entity wps_helpme;
-.entity wps_flagbase;
-.entity wps_flagcarrier;
-.entity wps_flagdropped;
-.entity wps_flagreturn;
-.entity wps_enemyflagcarrier;
-.float wps_helpme_time;
-bool wpforenemy_announced;
-float wpforenemy_nextthink;
-
-// statuses
-const int FLAG_BASE = 1;
-const int FLAG_DROPPED = 2;
-const int FLAG_CARRY = 3;
-const int FLAG_PASSING = 4;
-
-const int DROP_NORMAL = 1;
-const int DROP_THROW = 2;
-const int DROP_PASS = 3;
-const int DROP_RESET = 4;
-
-const int PICKUP_BASE = 1;
-const int PICKUP_DROPPED = 2;
-
-const int CAPTURE_NORMAL = 1;
-const int CAPTURE_DROPPED = 2;
-
-const int RETURN_TIMEOUT = 1;
-const int RETURN_DROPPED = 2;
-const int RETURN_DAMAGE = 3;
-const int RETURN_SPEEDRUN = 4;
-const int RETURN_NEEDKILL = 5;
-
-bool ctf_Stalemate_Customize(entity this, entity client);
-
-void ctf_Handle_Throw(entity player, entity receiver, float droptype);
-
-// flag properties
-#define ctf_spawnorigin dropped_origin
-bool ctf_stalemate; // indicates that a stalemate is active
-float ctf_captimerecord; // record time for capturing the flag
-.float ctf_pickuptime;
-.float ctf_droptime;
-.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
-.entity ctf_dropper; // don't allow spam of dropping the flag
-.int max_flag_health;
-.float next_take_time;
-.bool ctf_flagdamaged_byworld;
-int ctf_teams;
-.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
-
-// passing/throwing properties
-.float pass_distance;
-.entity pass_sender;
-.entity pass_target;
-.float throw_antispam;
-.float throw_prevtime;
-.int throw_count;
-
-// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
-.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
-float ctf_captureshield_min_negscore; // punish at -20 points
-float ctf_captureshield_max_ratio; // punish at most 30% of each team
-float ctf_captureshield_force; // push force of the shield
-
-// 1 flag ctf
-bool ctf_oneflag; // indicates whether or not a neutral flag has been found
-
-// bot player logic
-const int HAVOCBOT_CTF_ROLE_NONE = 0;
-const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
-const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
-const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
-const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
-const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
-const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
-
-.bool havocbot_cantfindflag;
-
-void havocbot_role_ctf_setrole(entity bot, int role);
-
-// team checking
-#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
-#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
-#endif
-
 const int CTF_RED_FLAG_TAKEN                   = 1;
 const int CTF_RED_FLAG_LOST                            = 2;
 const int CTF_RED_FLAG_CARRYING                        = 3;
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc
new file mode 100644 (file)
index 0000000..2696a4e
--- /dev/null
@@ -0,0 +1,2810 @@
+#include "sv_ctf.qh"
+
+#include <common/effects/all.qh>
+#include <common/vehicles/all.qh>
+#include <server/teamplay.qh>
+
+#include <lib/warpzone/common.qh>
+
+bool autocvar_g_ctf_allow_vehicle_carry;
+bool autocvar_g_ctf_allow_vehicle_touch;
+bool autocvar_g_ctf_allow_monster_touch;
+bool autocvar_g_ctf_throw;
+float autocvar_g_ctf_throw_angle_max;
+float autocvar_g_ctf_throw_angle_min;
+int autocvar_g_ctf_throw_punish_count;
+float autocvar_g_ctf_throw_punish_delay;
+float autocvar_g_ctf_throw_punish_time;
+float autocvar_g_ctf_throw_strengthmultiplier;
+float autocvar_g_ctf_throw_velocity_forward;
+float autocvar_g_ctf_throw_velocity_up;
+float autocvar_g_ctf_drop_velocity_up;
+float autocvar_g_ctf_drop_velocity_side;
+bool autocvar_g_ctf_oneflag_reverse;
+bool autocvar_g_ctf_portalteleport;
+bool autocvar_g_ctf_pass;
+float autocvar_g_ctf_pass_arc;
+float autocvar_g_ctf_pass_arc_max;
+float autocvar_g_ctf_pass_directional_max;
+float autocvar_g_ctf_pass_directional_min;
+float autocvar_g_ctf_pass_radius;
+float autocvar_g_ctf_pass_wait;
+bool autocvar_g_ctf_pass_request;
+float autocvar_g_ctf_pass_turnrate;
+float autocvar_g_ctf_pass_timelimit;
+float autocvar_g_ctf_pass_velocity;
+bool autocvar_g_ctf_dynamiclights;
+float autocvar_g_ctf_flag_collect_delay;
+float autocvar_g_ctf_flag_damageforcescale;
+bool autocvar_g_ctf_flag_dropped_waypoint;
+bool autocvar_g_ctf_flag_dropped_floatinwater;
+bool autocvar_g_ctf_flag_glowtrails;
+int autocvar_g_ctf_flag_health;
+bool autocvar_g_ctf_flag_return;
+bool autocvar_g_ctf_flag_return_carrying;
+float autocvar_g_ctf_flag_return_carried_radius;
+float autocvar_g_ctf_flag_return_time;
+bool autocvar_g_ctf_flag_return_when_unreachable;
+float autocvar_g_ctf_flag_return_damage;
+float autocvar_g_ctf_flag_return_damage_delay;
+float autocvar_g_ctf_flag_return_dropped;
+float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
+float autocvar_g_ctf_flagcarrier_auto_helpme_time;
+float autocvar_g_ctf_flagcarrier_selfdamagefactor;
+float autocvar_g_ctf_flagcarrier_selfforcefactor;
+float autocvar_g_ctf_flagcarrier_damagefactor;
+float autocvar_g_ctf_flagcarrier_forcefactor;
+//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
+bool autocvar_g_ctf_fullbrightflags;
+bool autocvar_g_ctf_ignore_frags;
+bool autocvar_g_ctf_score_ignore_fields;
+int autocvar_g_ctf_score_capture;
+int autocvar_g_ctf_score_capture_assist;
+int autocvar_g_ctf_score_kill;
+int autocvar_g_ctf_score_penalty_drop;
+int autocvar_g_ctf_score_penalty_returned;
+int autocvar_g_ctf_score_pickup_base;
+int autocvar_g_ctf_score_pickup_dropped_early;
+int autocvar_g_ctf_score_pickup_dropped_late;
+int autocvar_g_ctf_score_return;
+float autocvar_g_ctf_shield_force;
+float autocvar_g_ctf_shield_max_ratio;
+int autocvar_g_ctf_shield_min_negscore;
+bool autocvar_g_ctf_stalemate;
+int autocvar_g_ctf_stalemate_endcondition;
+float autocvar_g_ctf_stalemate_time;
+bool autocvar_g_ctf_reverse;
+float autocvar_g_ctf_dropped_capture_delay;
+float autocvar_g_ctf_dropped_capture_radius;
+
+void ctf_FakeTimeLimit(entity e, float t)
+{
+       msg_entity = e;
+       WriteByte(MSG_ONE, 3); // svc_updatestat
+       WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
+       if(t < 0)
+               WriteCoord(MSG_ONE, autocvar_timelimit);
+       else
+               WriteCoord(MSG_ONE, (t + 1) / 60);
+}
+
+void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
+               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ctf_CaptureRecord(entity flag, entity player)
+{
+       float cap_record = ctf_captimerecord;
+       float cap_time = (time - flag.ctf_pickuptime);
+       string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
+
+       // notify about shit
+       if(ctf_oneflag)
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
+       else if(!ctf_captimerecord)
+               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
+       else if(cap_time < cap_record)
+               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+       else
+               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+
+       // write that shit in the database
+       if(!ctf_oneflag) // but not in 1-flag mode
+       if((!ctf_captimerecord) || (cap_time < cap_record))
+       {
+               ctf_captimerecord = cap_time;
+               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
+               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
+               write_recordmarker(player, flag.ctf_pickuptime, cap_time);
+       }
+
+       if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
+               race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
+}
+
+bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
+{
+       int num_perteam = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
+
+       // automatically return if there's only 1 player on the team
+       return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
+               && flag.team);
+}
+
+bool ctf_Return_Customize(entity this, entity client)
+{
+       // only to the carrier
+       return boolean(client == this.owner);
+}
+
+void ctf_FlagcarrierWaypoints(entity player)
+{
+       WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
+       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
+       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+       WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+
+       if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
+       {
+               if(!player.wps_enemyflagcarrier)
+               {
+                       entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
+                       wp.colormod = WPCOLOR_ENEMYFC(player.team);
+                       setcefc(wp, ctf_Stalemate_Customize);
+
+                       if(IS_REAL_CLIENT(player) && !ctf_stalemate)
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
+               }
+
+               if(!player.wps_flagreturn)
+               {
+                       entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
+                       owp.colormod = '0 0.8 0.8';
+                       //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
+                       setcefc(owp, ctf_Return_Customize);
+               }
+       }
+}
+
+void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
+{
+       float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
+       float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
+       float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
+       //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
+
+       vector targpos;
+       if(current_height) // make sure we can actually do this arcing path
+       {
+               targpos = (to + ('0 0 1' * current_height));
+               WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+               if(trace_fraction < 1)
+               {
+                       //print("normal arc line failed, trying to find new pos...");
+                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
+                       targpos = (trace_endpos + eZ * FLAG_PASS_ARC_OFFSET_Z);
+                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+                       if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
+                       /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
+               }
+       }
+       else { targpos = to; }
+
+       //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+
+       vector desired_direction = normalize(targpos - from);
+       if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
+       else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
+}
+
+bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
+{
+       if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
+       {
+               // directional tracing only
+               float spreadlimit;
+               makevectors(passer_angle);
+
+               // find the closest point on the enemy to the center of the attack
+               float h; // hypotenuse, which is the distance between attacker to head
+               float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
+
+               h = vlen(head_center - passer_center);
+               a = h * (normalize(head_center - passer_center) * v_forward);
+
+               vector nearest_on_line = (passer_center + a * v_forward);
+               float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
+
+               spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
+               spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
+
+               if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
+                       { return true; }
+               else
+                       { return false; }
+       }
+       else { return true; }
+}
+
+
+// =======================
+// CaptureShield Functions
+// =======================
+
+bool ctf_CaptureShield_CheckStatus(entity p)
+{
+       int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
+       int players_worseeq, players_total;
+
+       if(ctf_captureshield_max_ratio <= 0)
+               return false;
+
+       s  = GameRules_scoring_add(p, CTF_CAPS, 0);
+       s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
+       s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
+       s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
+
+       sr = ((s - s2) + (s3 + s4));
+
+       if(sr >= -ctf_captureshield_min_negscore)
+               return false;
+
+       players_total = players_worseeq = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if(DIFF_TEAM(it, p))
+                       continue;
+               se  = GameRules_scoring_add(it, CTF_CAPS, 0);
+               se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
+               se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
+               se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
+
+               ser = ((se - se2) + (se3 + se4));
+
+               if(ser <= sr)
+                       ++players_worseeq;
+               ++players_total;
+       });
+
+       // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
+       // use this rule here
+
+       if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
+               return false;
+
+       return true;
+}
+
+void ctf_CaptureShield_Update(entity player, bool wanted_status)
+{
+       bool updated_status = ctf_CaptureShield_CheckStatus(player);
+       if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
+               player.ctf_captureshielded = updated_status;
+       }
+}
+
+bool ctf_CaptureShield_Customize(entity this, entity client)
+{
+       if(!client.ctf_captureshielded) { return false; }
+       if(CTF_SAMETEAM(this, client)) { return false; }
+
+       return true;
+}
+
+void ctf_CaptureShield_Touch(entity this, entity toucher)
+{
+       if(!toucher.ctf_captureshielded) { return; }
+       if(CTF_SAMETEAM(this, toucher)) { return; }
+
+       vector mymid = (this.absmin + this.absmax) * 0.5;
+       vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
+
+       Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
+       if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
+}
+
+void ctf_CaptureShield_Spawn(entity flag)
+{
+       entity shield = new(ctf_captureshield);
+
+       shield.enemy = flag;
+       shield.team = flag.team;
+       settouch(shield, ctf_CaptureShield_Touch);
+       setcefc(shield, ctf_CaptureShield_Customize);
+       shield.effects = EF_ADDITIVE;
+       set_movetype(shield, MOVETYPE_NOCLIP);
+       shield.solid = SOLID_TRIGGER;
+       shield.avelocity = '7 0 11';
+       shield.scale = 0.5;
+
+       setorigin(shield, flag.origin);
+       setmodel(shield, MDL_CTF_SHIELD);
+       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+}
+
+
+// ====================
+// Drop/Pass/Throw Code
+// ====================
+
+void ctf_Handle_Drop(entity flag, entity player, int droptype)
+{
+       // declarations
+       player = (player ? player : flag.pass_sender);
+
+       // main
+       set_movetype(flag, MOVETYPE_TOSS);
+       flag.takedamage = DAMAGE_YES;
+       flag.angles = '0 0 0';
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       flag.ctf_droptime = time;
+       flag.ctf_dropper = player;
+       flag.ctf_status = FLAG_DROPPED;
+
+       // messages and sounds
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
+       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
+       ctf_EventLog("dropped", player.team, player);
+
+       // scoring
+       GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
+       GameRules_scoring_add(player, CTF_DROPS, 1);
+
+       // waypoints
+       if(autocvar_g_ctf_flag_dropped_waypoint) {
+               entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
+               wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
+       }
+
+       if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
+       {
+               WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
+               WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH));
+       }
+
+       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+
+       if(droptype == DROP_PASS)
+       {
+               flag.pass_distance = 0;
+               flag.pass_sender = NULL;
+               flag.pass_target = NULL;
+       }
+}
+
+void ctf_Handle_Retrieve(entity flag, entity player)
+{
+       entity sender = flag.pass_sender;
+
+       // transfer flag to player
+       flag.owner = player;
+       flag.owner.flagcarried = flag;
+       GameRules_scoring_vip(player, true);
+
+       // reset flag
+       if(player.vehicle)
+       {
+               setattachment(flag, player.vehicle, "");
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.scale = VEHICLE_FLAG_SCALE;
+       }
+       else
+       {
+               setattachment(flag, player, "");
+               setorigin(flag, FLAG_CARRY_OFFSET);
+       }
+       set_movetype(flag, MOVETYPE_NONE);
+       flag.takedamage = DAMAGE_NO;
+       flag.solid = SOLID_NOT;
+       flag.angles = '0 0 0';
+       flag.ctf_status = FLAG_CARRY;
+
+       // messages and sounds
+       _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
+       ctf_EventLog("receive", flag.team, player);
+
+       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+               if(it == sender)
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
+               else if(it == player)
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
+               else if(SAME_TEAM(it, sender))
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
+       });
+
+       // create new waypoint
+       ctf_FlagcarrierWaypoints(player);
+
+       sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
+       player.throw_antispam = sender.throw_antispam;
+
+       flag.pass_distance = 0;
+       flag.pass_sender = NULL;
+       flag.pass_target = NULL;
+}
+
+void ctf_Handle_Throw(entity player, entity receiver, int droptype)
+{
+       entity flag = player.flagcarried;
+       vector targ_origin, flag_velocity;
+
+       if(!flag) { return; }
+       if((droptype == DROP_PASS) && !receiver) { return; }
+
+       if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
+
+       // reset the flag
+       setattachment(flag, NULL, "");
+       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+       flag.owner.flagcarried = NULL;
+       GameRules_scoring_vip(flag.owner, false);
+       flag.owner = NULL;
+       flag.solid = SOLID_TRIGGER;
+       flag.ctf_dropper = player;
+       flag.ctf_droptime = time;
+
+       flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
+
+       switch(droptype)
+       {
+               case DROP_PASS:
+               {
+                       // warpzone support:
+                       // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
+                       // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
+                       WarpZone_RefSys_Copy(flag, receiver);
+                       WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
+                       targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
+
+                       flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' *  player.origin.x) + ('0 1 0' *  player.origin.y))); // for the sake of this check, exclude Z axis
+                       ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
+
+                       // main
+                       set_movetype(flag, MOVETYPE_FLY);
+                       flag.takedamage = DAMAGE_NO;
+                       flag.pass_sender = player;
+                       flag.pass_target = receiver;
+                       flag.ctf_status = FLAG_PASSING;
+
+                       // other
+                       _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
+                       ctf_EventLog("pass", flag.team, player);
+                       break;
+               }
+
+               case DROP_THROW:
+               {
+                       makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
+
+                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
+                       flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
+                       ctf_Handle_Drop(flag, player, droptype);
+                       navigation_dynamicgoal_set(flag, player);
+                       break;
+               }
+
+               case DROP_RESET:
+               {
+                       flag.velocity = '0 0 0'; // do nothing
+                       break;
+               }
+
+               default:
+               case DROP_NORMAL:
+               {
+                       flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
+                       ctf_Handle_Drop(flag, player, droptype);
+                       navigation_dynamicgoal_set(flag, player);
+                       break;
+               }
+       }
+
+       // kill old waypointsprite
+       WaypointSprite_Ping(player.wps_flagcarrier);
+       WaypointSprite_Kill(player.wps_flagcarrier);
+
+       if(player.wps_enemyflagcarrier)
+               WaypointSprite_Kill(player.wps_enemyflagcarrier);
+
+       if(player.wps_flagreturn)
+               WaypointSprite_Kill(player.wps_flagreturn);
+
+       // captureshield
+       ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
+}
+
+void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
+{
+       return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
+}
+
+// ==============
+// Event Handlers
+// ==============
+
+void nades_GiveBonus(entity player, float score);
+
+void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
+{
+       entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
+       entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
+       entity player_team_flag = NULL, tmp_entity;
+       float old_time, new_time;
+
+       if(!player) { return; } // without someone to give the reward to, we can't possibly cap
+       if(CTF_DIFFTEAM(player, flag)) { return; }
+       if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
+
+       if (toucher.goalentity == flag.bot_basewaypoint)
+               toucher.goalentity_lock_timeout = 0;
+
+       if(ctf_oneflag)
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       if(SAME_TEAM(tmp_entity, player))
+       {
+               player_team_flag = tmp_entity;
+               break;
+       }
+
+       nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
+
+       player.throw_prevtime = time;
+       player.throw_count = 0;
+
+       // messages and sounds
+       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
+       ctf_CaptureRecord(enemy_flag, player);
+       _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
+
+       switch(capturetype)
+       {
+               case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
+               case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
+               default: break;
+       }
+
+       // scoring
+       float pscore = 0;
+       if(enemy_flag.score_capture || flag.score_capture)
+               pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
+       GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
+       float capscore = 0;
+       if(enemy_flag.score_team_capture || flag.score_team_capture)
+               capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
+       GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
+
+       old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
+       new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
+       if(!old_time || new_time < old_time)
+               GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
+
+       // effects
+       Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
+       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+
+       // other
+       if(capturetype == CAPTURE_NORMAL)
+       {
+               WaypointSprite_Kill(player.wps_flagcarrier);
+               if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
+
+               if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
+                       { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
+       }
+
+       flag.enemy = toucher;
+
+       // reset the flag
+       player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
+       ctf_RespawnFlag(enemy_flag);
+}
+
+void ctf_Handle_Return(entity flag, entity player)
+{
+       // messages and sounds
+       if(IS_MONSTER(player))
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
+       }
+       else if(flag.team)
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
+       }
+       _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
+       ctf_EventLog("return", flag.team, player);
+
+       // scoring
+       if(IS_PLAYER(player))
+       {
+               GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
+               GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
+
+               nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
+       }
+
+       TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
+
+       if(flag.ctf_dropper)
+       {
+               GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
+               ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
+               flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
+       }
+
+       // other
+       if(player.flagcarried == flag)
+               WaypointSprite_Kill(player.wps_flagcarrier);
+
+       flag.enemy = player;
+
+       // reset the flag
+       ctf_RespawnFlag(flag);
+}
+
+void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
+{
+       // declarations
+       float pickup_dropped_score; // used to calculate dropped pickup score
+
+       // attach the flag to the player
+       flag.owner = player;
+       player.flagcarried = flag;
+       GameRules_scoring_vip(player, true);
+       if(player.vehicle)
+       {
+               setattachment(flag, player.vehicle, "");
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.scale = VEHICLE_FLAG_SCALE;
+       }
+       else
+       {
+               setattachment(flag, player, "");
+               setorigin(flag, FLAG_CARRY_OFFSET);
+       }
+
+       // flag setup
+       set_movetype(flag, MOVETYPE_NONE);
+       flag.takedamage = DAMAGE_NO;
+       flag.solid = SOLID_NOT;
+       flag.angles = '0 0 0';
+       flag.ctf_status = FLAG_CARRY;
+
+       switch(pickuptype)
+       {
+               case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
+               case PICKUP_DROPPED: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
+               default: break;
+       }
+
+       // messages and sounds
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
+       if(ctf_stalemate)
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
+       if(!flag.team)
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
+       else if(CTF_DIFFTEAM(player, flag))
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
+       else
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
+
+       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+
+       if(!flag.team)
+               FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
+
+       if(flag.team)
+               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
+                       if(CTF_SAMETEAM(flag, it))
+                       if(SAME_TEAM(player, it))
+                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+                       else
+                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
+               });
+
+       _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
+
+       // scoring
+       GameRules_scoring_add(player, CTF_PICKUPS, 1);
+       nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
+       switch(pickuptype)
+       {
+               case PICKUP_BASE:
+               {
+                       GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
+                       ctf_EventLog("steal", flag.team, player);
+                       break;
+               }
+
+               case PICKUP_DROPPED:
+               {
+                       pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
+                       pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
+                       LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
+                       GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
+                       ctf_EventLog("pickup", flag.team, player);
+                       break;
+               }
+
+               default: break;
+       }
+
+       // speedrunning
+       if(pickuptype == PICKUP_BASE)
+       {
+               flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
+               if((player.speedrunning) && (ctf_captimerecord))
+                       ctf_FakeTimeLimit(player, time + ctf_captimerecord);
+       }
+
+       // effects
+       Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
+
+       // waypoints
+       if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
+       ctf_FlagcarrierWaypoints(player);
+       WaypointSprite_Ping(player.wps_flagcarrier);
+}
+
+
+// ===================
+// Main Flag Functions
+// ===================
+
+void ctf_CheckFlagReturn(entity flag, int returntype)
+{
+       if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
+       {
+               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH)); }
+
+               if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+               {
+                       switch(returntype)
+                       {
+                               case RETURN_DROPPED:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
+                               case RETURN_DAMAGE:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
+                               case RETURN_SPEEDRUN:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
+                               case RETURN_NEEDKILL:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
+                               default:
+                               case RETURN_TIMEOUT:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
+                       }
+                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
+                       ctf_EventLog("returned", flag.team, NULL);
+                       flag.enemy = NULL;
+                       ctf_RespawnFlag(flag);
+               }
+       }
+}
+
+bool ctf_Stalemate_Customize(entity this, entity client)
+{
+       // make spectators see what the player would see
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.owner;
+
+       // team waypoints
+       //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
+       if(SAME_TEAM(wp_owner, e)) { return false; }
+       if(!IS_PLAYER(e)) { return false; }
+
+       return true;
+}
+
+void ctf_CheckStalemate()
+{
+       // declarations
+       int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
+       entity tmp_entity;
+
+       entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
+
+       // build list of stale flags
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       {
+               if(autocvar_g_ctf_stalemate)
+               if(tmp_entity.ctf_status != FLAG_BASE)
+               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
+               {
+                       tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
+                       ctf_staleflaglist = tmp_entity;
+
+                       switch(tmp_entity.team)
+                       {
+                               case NUM_TEAM_1: ++stale_red_flags; break;
+                               case NUM_TEAM_2: ++stale_blue_flags; break;
+                               case NUM_TEAM_3: ++stale_yellow_flags; break;
+                               case NUM_TEAM_4: ++stale_pink_flags; break;
+                               default: ++stale_neutral_flags; break;
+                       }
+               }
+       }
+
+       if(ctf_oneflag)
+               stale_flags = (stale_neutral_flags >= 1);
+       else
+               stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
+
+       if(ctf_oneflag && stale_flags == 1)
+               ctf_stalemate = true;
+       else if(stale_flags >= 2)
+               ctf_stalemate = true;
+       else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
+               { ctf_stalemate = false; wpforenemy_announced = false; }
+       else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
+               { ctf_stalemate = false; wpforenemy_announced = false; }
+
+       // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
+       if(ctf_stalemate)
+       {
+               for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
+               {
+                       if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
+                       {
+                               entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
+                               wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
+                               setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
+                       }
+               }
+
+               if (!wpforenemy_announced)
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
+
+                       wpforenemy_announced = true;
+               }
+       }
+}
+
+void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               if(autocvar_g_ctf_flag_return_damage_delay)
+                       this.ctf_flagdamaged_byworld = true;
+               else
+               {
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                       ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+               }
+               return;
+       }
+       if(autocvar_g_ctf_flag_return_damage)
+       {
+               // reduce health and check if it should be returned
+               TakeResource(this, RESOURCE_HEALTH, damage);
+               ctf_CheckFlagReturn(this, RETURN_DAMAGE);
+               return;
+       }
+}
+
+void ctf_FlagThink(entity this)
+{
+       // declarations
+       entity tmp_entity;
+
+       this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
+       // captureshield
+       if(this == ctf_worldflaglist) // only for the first flag
+               FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
+
+       // sanity checks
+       if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
+               LOG_TRACE("wtf the flag got squashed?");
+               tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
+               if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
+                       setsize(this, this.m_mins, this.m_maxs);
+       }
+
+       // main think method
+       switch(this.ctf_status)
+       {
+               case FLAG_BASE:
+               {
+                       if(autocvar_g_ctf_dropped_capture_radius)
+                       {
+                               for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+                                       if(tmp_entity.ctf_status == FLAG_DROPPED)
+                                       if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
+                                       if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
+                                               ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
+                       }
+                       return;
+               }
+
+               case FLAG_DROPPED:
+               {
+                       this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
+
+                       if(autocvar_g_ctf_flag_dropped_floatinwater)
+                       {
+                               vector midpoint = ((this.absmin + this.absmax) * 0.5);
+                               if(pointcontents(midpoint) == CONTENT_WATER)
+                               {
+                                       this.velocity = this.velocity * 0.5;
+
+                                       if (pointcontents(midpoint + eZ * FLAG_FLOAT_OFFSET_Z) == CONTENT_WATER)
+                                               { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+                                       else
+                                               { set_movetype(this, MOVETYPE_FLY); }
+                               }
+                               else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
+                       }
+                       if(autocvar_g_ctf_flag_return_dropped)
+                       {
+                               if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
+                               {
+                                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                                       ctf_CheckFlagReturn(this, RETURN_DROPPED);
+                                       return;
+                               }
+                       }
+                       if(this.ctf_flagdamaged_byworld)
+                       {
+                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
+                               ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+                               return;
+                       }
+                       else if(autocvar_g_ctf_flag_return_time)
+                       {
+                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
+                               ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
+                               return;
+                       }
+                       return;
+               }
+
+               case FLAG_CARRY:
+               {
+                       if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
+                       {
+                               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                               ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
+
+                               CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
+                               ImpulseCommands(this.owner);
+                       }
+                       if(autocvar_g_ctf_stalemate)
+                       {
+                               if(time >= wpforenemy_nextthink)
+                               {
+                                       ctf_CheckStalemate();
+                                       wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
+                               }
+                       }
+                       if(CTF_SAMETEAM(this, this.owner) && this.team)
+                       {
+                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+                                       ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
+                               else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
+                                       ctf_Handle_Return(this, this.owner);
+                       }
+                       return;
+               }
+
+               case FLAG_PASSING:
+               {
+                       vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
+                       targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
+                       WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
+
+                       if((this.pass_target == NULL)
+                               || (IS_DEAD(this.pass_target))
+                               || (this.pass_target.flagcarried)
+                               || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
+                               || ((trace_fraction < 1) && (trace_ent != this.pass_target))
+                               || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
+                       {
+                               // give up, pass failed
+                               ctf_Handle_Drop(this, NULL, DROP_PASS);
+                       }
+                       else
+                       {
+                               // still a viable target, go for it
+                               ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
+                       }
+                       return;
+               }
+
+               default: // this should never happen
+               {
+                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
+                       return;
+               }
+       }
+}
+
+METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
+{
+       return = false;
+       if(game_stopped) return;
+       if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
+
+       bool is_not_monster = (!IS_MONSTER(toucher));
+
+       // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               if(!autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+                       ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
+               }
+               if(!flag.ctf_flagdamaged_byworld) { return; }
+       }
+
+       // special touch behaviors
+       if(STAT(FROZEN, toucher)) { return; }
+       else if(IS_VEHICLE(toucher))
+       {
+               if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
+                       toucher = toucher.owner; // the player is actually the vehicle owner, not other
+               else
+                       return; // do nothing
+       }
+       else if(IS_MONSTER(toucher))
+       {
+               if(!autocvar_g_ctf_allow_monster_touch)
+                       return; // do nothing
+       }
+       else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
+       {
+               if(time > flag.wait) // if we haven't in a while, play a sound/effect
+               {
+                       Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
+                       _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       flag.wait = time + FLAG_TOUCHRATE;
+               }
+               return;
+       }
+       else if(IS_DEAD(toucher)) { return; }
+
+       switch(flag.ctf_status)
+       {
+               case FLAG_BASE:
+               {
+                       if(ctf_oneflag)
+                       {
+                               if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+                                       ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+                               else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                                       ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+                       }
+                       else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
+                               ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
+                       else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
+                       {
+                               ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
+                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
+                       }
+                       else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
+                       break;
+               }
+
+               case FLAG_DROPPED:
+               {
+                       if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
+                               ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
+                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+                               ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
+                       break;
+               }
+
+               case FLAG_CARRY:
+               {
+                       LOG_TRACE("Someone touched a flag even though it was being carried?");
+                       break;
+               }
+
+               case FLAG_PASSING:
+               {
+                       if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
+                       {
+                               if(DIFF_TEAM(toucher, flag.pass_sender))
+                               {
+                                       if(ctf_Immediate_Return_Allowed(flag, toucher))
+                                               ctf_Handle_Return(flag, toucher);
+                                       else if(is_not_monster && (!toucher.flagcarried))
+                                               ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
+                               }
+                               else if(!toucher.flagcarried)
+                                       ctf_Handle_Retrieve(flag, toucher);
+                       }
+                       break;
+               }
+       }
+}
+
+.float last_respawn;
+void ctf_RespawnFlag(entity flag)
+{
+       // check for flag respawn being called twice in a row
+       if(flag.last_respawn > time - 0.5)
+               { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
+
+       flag.last_respawn = time;
+
+       // reset the player (if there is one)
+       if((flag.owner) && (flag.owner.flagcarried == flag))
+       {
+               WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
+               WaypointSprite_Kill(flag.owner.wps_flagreturn);
+               WaypointSprite_Kill(flag.wps_flagcarrier);
+
+               flag.owner.flagcarried = NULL;
+               GameRules_scoring_vip(flag.owner, false);
+
+               if(flag.speedrunning)
+                       ctf_FakeTimeLimit(flag.owner, -1);
+       }
+
+       if((flag.owner) && (flag.owner.vehicle))
+               flag.scale = FLAG_SCALE;
+
+       if(flag.ctf_status == FLAG_DROPPED)
+               { WaypointSprite_Kill(flag.wps_flagdropped); }
+
+       // reset the flag
+       setattachment(flag, NULL, "");
+       setorigin(flag, flag.ctf_spawnorigin);
+
+       set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
+       flag.takedamage = DAMAGE_NO;
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       flag.solid = SOLID_TRIGGER;
+       flag.velocity = '0 0 0';
+       flag.angles = flag.mangle;
+       flag.flags = FL_ITEM | FL_NOTARGET;
+
+       flag.ctf_status = FLAG_BASE;
+       flag.owner = NULL;
+       flag.pass_distance = 0;
+       flag.pass_sender = NULL;
+       flag.pass_target = NULL;
+       flag.ctf_dropper = NULL;
+       flag.ctf_pickuptime = 0;
+       flag.ctf_droptime = 0;
+       flag.ctf_flagdamaged_byworld = false;
+       navigation_dynamicgoal_unset(flag);
+
+       ctf_CheckStalemate();
+}
+
+void ctf_Reset(entity this)
+{
+       if(this.owner && IS_PLAYER(this.owner))
+               ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+
+       this.enemy = NULL;
+       ctf_RespawnFlag(this);
+}
+
+bool ctf_FlagBase_Customize(entity this, entity client)
+{
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.owner;
+       entity flag = e.flagcarried;
+       if(flag && CTF_SAMETEAM(e, flag))
+               return false;
+       if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
+               return false;
+       return true;
+}
+
+void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
+{
+       // bot waypoints
+       waypoint_spawnforitem_force(this, this.origin);
+       navigation_dynamicgoal_init(this, true);
+
+       // waypointsprites
+       entity basename;
+       switch (this.team)
+       {
+               case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
+               case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
+               case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
+               case NUM_TEAM_4: basename = WP_FlagBasePink; break;
+               default: basename = WP_FlagBaseNeutral; break;
+       }
+
+       entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
+       wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
+       WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+       setcefc(wp, ctf_FlagBase_Customize);
+
+       // captureshield setup
+       ctf_CaptureShield_Spawn(this);
+}
+
+.bool pushable;
+
+void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+{
+       // main setup
+       flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
+       ctf_worldflaglist = flag;
+
+       setattachment(flag, NULL, "");
+
+       flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
+       flag.team = teamnumber;
+       flag.classname = "item_flag_team";
+       flag.target = "###item###"; // for finding the nearest item using findnearest
+       flag.flags = FL_ITEM | FL_NOTARGET;
+       IL_PUSH(g_items, flag);
+       flag.solid = SOLID_TRIGGER;
+       flag.takedamage = DAMAGE_NO;
+       flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
+       flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       flag.event_damage = ctf_FlagDamage;
+       flag.pushable = true;
+       flag.teleportable = TELEPORT_NORMAL;
+       flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
+       flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
+       flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
+       if(flag.damagedbycontents)
+               IL_PUSH(g_damagedbycontents, flag);
+       flag.velocity = '0 0 0';
+       flag.mangle = flag.angles;
+       flag.reset = ctf_Reset;
+       settouch(flag, ctf_FlagTouch);
+       setthink(flag, ctf_FlagThink);
+       flag.nextthink = time + FLAG_THINKRATE;
+       flag.ctf_status = FLAG_BASE;
+
+       // crudely force them all to 0
+       if(autocvar_g_ctf_score_ignore_fields)
+               flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
+
+       string teamname = Static_Team_ColorName_Lower(teamnumber);
+       // appearence
+       if(!flag.scale)                         { flag.scale = FLAG_SCALE; }
+       if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
+       if(flag.model == "")            { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
+       if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
+       if (flag.passeffect == "")      { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
+       if (flag.capeffect == "")       { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
+
+       // sounds
+#define X(s,b) \
+               if(flag.s == "") flag.s = b; \
+               precache_sound(flag.s);
+
+       X(snd_flag_taken,               strzone(SND(CTF_TAKEN(teamnumber))))
+       X(snd_flag_returned,    strzone(SND(CTF_RETURNED(teamnumber))))
+       X(snd_flag_capture,     strzone(SND(CTF_CAPTURE(teamnumber))))
+       X(snd_flag_dropped,     strzone(SND(CTF_DROPPED(teamnumber))))
+       X(snd_flag_respawn,     strzone(SND(CTF_RESPAWN)))
+       X(snd_flag_touch,               strzone(SND(CTF_TOUCH)))
+       X(snd_flag_pass,                strzone(SND(CTF_PASS)))
+#undef X
+
+       // precache
+       precache_model(flag.model);
+
+       // appearence
+       _setmodel(flag, flag.model); // precision set below
+       setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
+       flag.m_mins = flag.mins; // store these for squash checks
+       flag.m_maxs = flag.maxs;
+       setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
+
+       if(autocvar_g_ctf_flag_glowtrails)
+       {
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.glow_color = 251; break;
+                       case NUM_TEAM_2: flag.glow_color = 210; break;
+                       case NUM_TEAM_3: flag.glow_color = 110; break;
+                       case NUM_TEAM_4: flag.glow_color = 145; break;
+                       default:                 flag.glow_color = 254; break;
+               }
+               flag.glow_size = 25;
+               flag.glow_trail = 1;
+       }
+
+       flag.effects |= EF_LOWPRECISION;
+       if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
+       if(autocvar_g_ctf_dynamiclights)
+       {
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.effects |= EF_RED; break;
+                       case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
+                       case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
+                       case NUM_TEAM_4: flag.effects |= EF_RED; break;
+                       default:                 flag.effects |= EF_DIMLIGHT; break;
+               }
+       }
+
+       // flag placement
+       if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
+       {
+               flag.dropped_origin = flag.origin;
+               flag.noalign = true;
+               set_movetype(flag, MOVETYPE_NONE);
+       }
+       else // drop to floor, automatically find a platform and set that as spawn origin
+       {
+               flag.noalign = false;
+               droptofloor(flag);
+               set_movetype(flag, MOVETYPE_NONE);
+       }
+
+       InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_ctf_calculate_middlepoint()
+{
+       entity f;
+       vector s = '0 0 0';
+       vector fo = '0 0 0';
+       int n = 0;
+
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               fo = f.origin;
+               s = s + fo;
+               f = f.ctf_worldflagnext;
+               n++;
+       }
+       if(!n)
+               return;
+
+       havocbot_middlepoint = s / n;
+       havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
+
+       havocbot_symmetry_axis_m = 0;
+       havocbot_symmetry_axis_q = 0;
+       if(n == 2)
+       {
+               // for symmetrical editing of waypoints
+               entity f1 = ctf_worldflaglist;
+               entity f2 = f1.ctf_worldflagnext;
+               float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+               float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
+               havocbot_symmetry_axis_m = m;
+               havocbot_symmetry_axis_q = q;
+       }
+       havocbot_symmetry_origin_order = n;
+}
+
+
+entity havocbot_ctf_find_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if (CTF_SAMETEAM(bot, f))
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return NULL;
+}
+
+entity havocbot_ctf_find_enemy_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(bot, f))
+                       {
+                               if(f.team)
+                               {
+                                       if(bot.flagcarried)
+                                               return f;
+                               }
+                               else if(!bot.flagcarried)
+                                       return f;
+                       }
+               }
+               else if (CTF_DIFFTEAM(bot, f))
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return NULL;
+}
+
+int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+{
+       if (!teamplay)
+               return 0;
+
+       int c = 0;
+
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
+                       continue;
+
+               if(vdist(it.origin - org, <, tc_radius))
+                       ++c;
+       });
+
+       return c;
+}
+
+// unused
+#if 0
+void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (CTF_SAMETEAM(this, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(this, head, ratingscale, 10000);
+}
+#endif
+
+void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (CTF_SAMETEAM(this, head))
+               {
+                       if (this.flagcarried)
+                       if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
+                       {
+                               head = head.ctf_worldflagnext; // skip base if it has a different group
+                               continue;
+                       }
+                       break;
+               }
+               head = head.ctf_worldflagnext;
+       }
+       if (!head)
+               return;
+
+       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(this, head))
+                       {
+                               if(head.team)
+                               {
+                                       if(this.flagcarried)
+                                               break;
+                               }
+                               else if(!this.flagcarried)
+                                       break;
+                       }
+               }
+               else if(CTF_DIFFTEAM(this, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+       {
+               if (head.ctf_status == FLAG_CARRY)
+               {
+                       // adjust rating of our flag carrier depending on his health
+                       head = head.tag_entity;
+                       float f = bound(0, (GetResourceAmount(head, RESOURCE_HEALTH) + GetResourceAmount(head, RESOURCE_ARMOR)) / 100, 2) - 1;
+                       ratingscale += ratingscale * f * 0.1;
+               }
+               navigation_routerating(this, head, ratingscale, 10000);
+       }
+}
+
+void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
+{
+       // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
+       /*
+       if (!bot_waypoints_for_items)
+       {
+               havocbot_goalrating_ctf_enemyflag(this, ratingscale);
+               return;
+       }
+       */
+       entity head;
+
+       head = havocbot_ctf_find_enemy_flag(this);
+
+       if (!head)
+               return;
+
+       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
+{
+       entity mf;
+
+       mf = havocbot_ctf_find_flag(this);
+
+       if(mf.ctf_status == FLAG_BASE)
+               return;
+
+       if(mf.tag_entity)
+               navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               // flag is out in the field
+               if(head.ctf_status != FLAG_BASE)
+               if(head.tag_entity==NULL)       // dropped
+               {
+                       if(df_radius)
+                       {
+                               if(vdist(org - head.origin, <, df_radius))
+                                       navigation_routerating(this, head, ratingscale, 10000);
+                       }
+                       else
+                               navigation_routerating(this, head, ratingscale, 10000);
+               }
+
+               head = head.ctf_worldflagnext;
+       }
+}
+
+void havocbot_ctf_reset_role(entity this)
+{
+       float cdefense, cmiddle, coffense;
+       entity mf, ef;
+
+       if(IS_DEAD(this))
+               return;
+
+       // Check ctf flags
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(this);
+       ef = havocbot_ctf_find_enemy_flag(this);
+
+       // Retrieve stolen flag
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       // If enemy flag is taken go to the middle to intercept pursuers
+       if(ef.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // if there is no one else on the team switch to offense
+       int count = 0;
+       // don't check if this bot is a player since it isn't true when the bot is added to the server
+       FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
+
+       if (count == 0)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+               return;
+       }
+       else if (time < CS(this).jointime + 1)
+       {
+               // if bots spawn all at once set good default roles
+               if (count == 1)
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+                       return;
+               }
+               else if (count == 2)
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+                       return;
+               }
+       }
+
+       // Evaluate best position to take
+       // Count mates on middle position
+       cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
+
+       // Count mates on defense position
+       cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
+
+       // Count mates on offense position
+       coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
+
+       if(cdefense<=coffense)
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+       else if(coffense<=cmiddle)
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+       else
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+
+       // if bots spawn all at once assign them a more appropriated role after a while
+       if (time < CS(this).jointime + 1 && count > 2)
+               this.havocbot_role_timeout = time + 10 + random() * 10;
+}
+
+bool havocbot_ctf_is_basewaypoint(entity item)
+{
+       if (item.classname != "waypoint")
+               return false;
+
+       entity head = ctf_worldflaglist;
+       while (head)
+       {
+               if (item == head.bot_basewaypoint)
+                       return true;
+               head = head.ctf_worldflagnext;
+       }
+       return false;
+}
+
+void havocbot_role_ctf_carrier(entity this)
+{
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried == NULL)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               // role: carrier
+               entity mf = havocbot_ctf_find_flag(this);
+               vector base_org = mf.dropped_origin;
+               float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
+               if(ctf_oneflag)
+                       havocbot_goalrating_ctf_enemybase(this, base_rating);
+               else
+                       havocbot_goalrating_ctf_ourbase(this, base_rating);
+
+               // start collecting items very close to the bot but only inside of own base radius
+               if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
+                       havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+
+               havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+
+               entity goal = this.goalentity;
+               if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+                       this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
+
+               if (goal)
+                       this.havocbot_cantfindflag = time + 10;
+               else if (time > this.havocbot_cantfindflag)
+               {
+                       // Can't navigate to my own base, suicide!
+                       // TODO: drop it and wander around
+                       Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+                       return;
+               }
+       }
+}
+
+void havocbot_role_ctf_escort(entity this)
+{
+       entity mf, ef;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If enemy flag is back on the base switch to previous role
+       ef = havocbot_ctf_find_enemy_flag(this);
+       if(ef.ctf_status==FLAG_BASE)
+       {
+               this.havocbot_role = this.havocbot_previous_role;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+       if (ef.ctf_status == FLAG_DROPPED)
+       {
+               navigation_goalrating_timeout_expire(this, 1);
+               return;
+       }
+
+       // If the flag carrier reached the base switch to defense
+       mf = havocbot_ctf_find_flag(this);
+       if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+       {
+               this.havocbot_role_timeout = time + random() * 30 + 60;
+       }
+
+       // If nothing happened just switch to previous role
+       if (time > this.havocbot_role_timeout)
+       {
+               this.havocbot_role = this.havocbot_previous_role;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // Chase the flag carrier
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               // role: escort
+               havocbot_goalrating_ctf_enemyflag(this, 10000);
+               havocbot_goalrating_ctf_ourstolenflag(this, 6000);
+               havocbot_goalrating_items(this, 21000, this.origin, 10000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_offense(entity this)
+{
+       entity mf, ef;
+       vector pos;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // Check flags
+       mf = havocbot_ctf_find_flag(this);
+       ef = havocbot_ctf_find_enemy_flag(this);
+
+       // Own flag stolen
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               if(mf.tag_entity)
+                       pos = mf.tag_entity.origin;
+               else
+                       pos = mf.origin;
+
+               // Try to get it if closer than the enemy base
+               if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+                       return;
+               }
+       }
+
+       // Escort flag carrier
+       if(ef.ctf_status!=FLAG_BASE)
+       {
+               if(ef.tag_entity)
+                       pos = ef.tag_entity.origin;
+               else
+                       pos = ef.origin;
+
+               if(vdist(pos - mf.dropped_origin, >, 700))
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
+                       return;
+               }
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 120;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               // role: offense
+               havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 10000);
+               havocbot_goalrating_items(this, 22000, this.origin, 10000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever(entity this)
+{
+       entity mf;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If flag is back on the base switch to previous role
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status==FLAG_BASE)
+       {
+               if (mf.enemy == this) // did this bot return the flag?
+                       navigation_goalrating_timeout_force(this);
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 20;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               const float RT_RADIUS = 10000;
+
+               navigation_goalrating_start(this);
+
+               // role: retriever
+               havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+               havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
+               havocbot_goalrating_ctf_enemybase(this, 8000);
+               entity ef = havocbot_ctf_find_enemy_flag(this);
+               vector enemy_base_org = ef.dropped_origin;
+               // start collecting items very close to the bot but only inside of enemy base radius
+               if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
+                       havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+               havocbot_goalrating_items(this, 18000, this.origin, havocbot_middlepoint_radius);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_middle(entity this)
+{
+       entity mf;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 10;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               vector org;
+
+               org = havocbot_middlepoint;
+               org.z = this.origin.z;
+
+               navigation_goalrating_start(this);
+
+               // role: middle
+               havocbot_goalrating_ctf_ourstolenflag(this, 8000);
+               havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 18000, this.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 3000);
+
+               navigation_goalrating_end(this);
+
+               entity goal = this.goalentity;
+               if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+                       this.goalentity_lock_timeout = time + 2;
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_defense(entity this)
+{
+       entity mf;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If own flag was captured
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 30;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+       if (navigation_goalrating_timeout(this))
+       {
+               vector org = mf.dropped_origin;
+
+               navigation_goalrating_start(this);
+
+               // if enemies are closer to our base, go there
+               entity closestplayer = NULL;
+               float distance, bestdistance = 10000;
+               FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
+                       distance = vlen(org - it.origin);
+                       if(distance<bestdistance)
+                       {
+                               closestplayer = it;
+                               bestdistance = distance;
+                       }
+               });
+
+               // role: defense
+               if(closestplayer)
+               if(DIFF_TEAM(closestplayer, this))
+               if(vdist(org - this.origin, >, 1000))
+               if(checkpvs(this.origin,closestplayer)||random()<0.5)
+                       havocbot_goalrating_ctf_ourbase(this, 10000);
+
+               havocbot_goalrating_ctf_ourstolenflag(this, 5000);
+               havocbot_goalrating_ctf_droppedflags(this, 6000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 18000, this.origin, 10000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_setrole(entity bot, int role)
+{
+       string s = "(null)";
+       switch(role)
+       {
+               case HAVOCBOT_CTF_ROLE_CARRIER:
+                       s = "carrier";
+                       bot.havocbot_role = havocbot_role_ctf_carrier;
+                       bot.havocbot_role_timeout = 0;
+                       bot.havocbot_cantfindflag = time + 10;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_force(bot);
+                       break;
+               case HAVOCBOT_CTF_ROLE_DEFENSE:
+                       s = "defense";
+                       bot.havocbot_role = havocbot_role_ctf_defense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_MIDDLE:
+                       s = "middle";
+                       bot.havocbot_role = havocbot_role_ctf_middle;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_OFFENSE:
+                       s = "offense";
+                       bot.havocbot_role = havocbot_role_ctf_offense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_RETRIEVER:
+                       s = "retriever";
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_retriever;
+                       bot.havocbot_role_timeout = time + 10;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_expire(bot, 2);
+                       break;
+               case HAVOCBOT_CTF_ROLE_ESCORT:
+                       s = "escort";
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_escort;
+                       bot.havocbot_role_timeout = time + 30;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_expire(bot, 2);
+                       break;
+       }
+       LOG_TRACE(bot.netname, " switched to ", s);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       int t = 0, t2 = 0, t3 = 0;
+       bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
+
+       // initially clear items so they can be set as necessary later.
+       STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING         | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
+                                                  | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
+                                                  | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
+                                                  | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
+                                                  | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
+                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
+
+       // scan through all the flags and notify the client about them
+       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING;              t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING;             t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING;   t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING;             t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
+               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
+
+               switch(flag.ctf_status)
+               {
+                       case FLAG_PASSING:
+                       case FLAG_CARRY:
+                       {
+                               if((flag.owner == player) || (flag.pass_sender == player))
+                                       STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
+                               else
+                                       STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
+                               break;
+                       }
+                       case FLAG_DROPPED:
+                       {
+                               STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
+                               break;
+                       }
+               }
+       }
+
+       // item for stopping players from capturing the flag too often
+       if(player.ctf_captureshielded)
+               STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
+
+       if(ctf_stalemate)
+               STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
+
+       // update the health of the flag carrier waypointsprite
+       if(player.wps_flagcarrier)
+               WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+}
+
+MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
+       if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
+                       frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
+               }
+               else // damage done to everyone else
+               {
+                       frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
+                       frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
+               }
+
+               M_ARGV(4, float) = frag_damage;
+               M_ARGV(6, vector) = frag_force;
+       }
+       else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
+       {
+               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
+               if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
+               {
+                       frag_target.wps_helpme_time = time;
+                       WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
+               }
+               // todo: add notification for when flag carrier needs help?
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
+       {
+               GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
+               GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
+       }
+
+       if(frag_target.flagcarried)
+       {
+               entity tmp_entity = frag_target.flagcarried;
+               ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
+               tmp_entity.ctf_dropper = NULL;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
+{
+       M_ARGV(2, float) = 0; // frag score
+       return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
+}
+
+void ctf_RemovePlayer(entity player)
+{
+       if(player.flagcarried)
+               { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
+
+       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag.pass_sender == player) { flag.pass_sender = NULL; }
+               if(flag.pass_target == player) { flag.pass_target = NULL; }
+               if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
+{
+       if(!autocvar_g_ctf_leaderboard)
+               return;
+
+       entity player = M_ARGV(0, entity);
+
+       if(IS_REAL_CLIENT(player))
+       {
+               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+               race_send_rankings_cnt(MSG_ONE);
+               for (int i = 1; i <= m; ++i)
+               {
+                       race_SendRankings(i, 0, 0, MSG_ONE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
+{
+       if(!autocvar_g_ctf_leaderboard)
+               return;
+
+       entity player = M_ARGV(0, entity);
+
+       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+       {
+               if (!player.stored_netname)
+                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
+               if(player.stored_netname != player.netname)
+               {
+                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+                       strcpy(player.stored_netname, player.netname);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.flagcarried)
+       if(!autocvar_g_ctf_portalteleport)
+               { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
+{
+       if(MUTATOR_RETURNVALUE || game_stopped) return;
+
+       entity player = M_ARGV(0, entity);
+
+       if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
+       {
+               // pass the flag to a team mate
+               if(autocvar_g_ctf_pass)
+               {
+                       entity head, closest_target = NULL;
+                       head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
+
+                       while(head) // find the closest acceptable target to pass to
+                       {
+                               if(IS_PLAYER(head) && !IS_DEAD(head))
+                               if(head != player && SAME_TEAM(head, player))
+                               if(!head.speedrunning && !head.vehicle)
+                               {
+                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+                                       vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
+                                       vector passer_center = CENTER_OR_VIEWOFS(player);
+
+                                       if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
+                                       {
+                                               if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
+                                               {
+                                                       if(IS_BOT_CLIENT(head))
+                                                       {
+                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
+                                                               ctf_Handle_Throw(head, player, DROP_PASS);
+                                                       }
+                                                       else
+                                                       {
+                                                               Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
+                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
+                                                       }
+                                                       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+                                                       return true;
+                                               }
+                                               else if(player.flagcarried && !head.flagcarried)
+                                               {
+                                                       if(closest_target)
+                                                       {
+                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
+                                                               if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
+                                                                       { closest_target = head; }
+                                                       }
+                                                       else { closest_target = head; }
+                                               }
+                                       }
+                               }
+                               head = head.chain;
+                       }
+
+                       if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
+               }
+
+               // throw the flag in front of you
+               if(autocvar_g_ctf_throw && player.flagcarried)
+               {
+                       if(player.throw_count == -1)
+                       {
+                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
+                               {
+                                       player.throw_prevtime = time;
+                                       player.throw_count = 1;
+                                       ctf_Handle_Throw(player, NULL, DROP_THROW);
+                                       return true;
+                               }
+                               else
+                               {
+                                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
+                                       return false;
+                               }
+                       }
+                       else
+                       {
+                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
+                               else { player.throw_count += 1; }
+                               if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
+
+                               player.throw_prevtime = time;
+                               ctf_Handle_Throw(player, NULL, DROP_THROW);
+                               return true;
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
+       {
+               player.wps_helpme_time = time;
+               WaypointSprite_HelpMePing(player.wps_flagcarrier);
+       }
+       else // create a normal help me waypointsprite
+       {
+               WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
+               WaypointSprite_Ping(player.wps_helpme);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
+{
+       entity player = M_ARGV(0, entity);
+       entity veh = M_ARGV(1, entity);
+
+       if(player.flagcarried)
+       {
+               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
+               {
+                       ctf_Handle_Throw(player, NULL, DROP_NORMAL);
+               }
+               else
+               {
+                       player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
+                       setattachment(player.flagcarried, veh, "");
+                       setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
+                       player.flagcarried.scale = VEHICLE_FLAG_SCALE;
+                       //player.flagcarried.angles = '0 0 0';
+               }
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.flagcarried)
+       {
+               setattachment(player.flagcarried, player, "");
+               setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
+               player.flagcarried.scale = FLAG_SCALE;
+               player.flagcarried.angles = '0 0 0';
+               player.flagcarried.nodrawtoclient = NULL;
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.flagcarried)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
+               ctf_RespawnFlag(player.flagcarried);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
+{
+       entity flag; // temporary entity for the search method
+
+       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               switch(flag.ctf_status)
+               {
+                       case FLAG_DROPPED:
+                       case FLAG_PASSING:
+                       {
+                               // lock the flag, game is over
+                               set_movetype(flag, MOVETYPE_NONE);
+                               flag.takedamage = DAMAGE_NO;
+                               flag.solid = SOLID_NOT;
+                               flag.nextthink = false; // stop thinking
+
+                               //dprint("stopping the ", flag.netname, " from moving.\n");
+                               break;
+                       }
+
+                       default:
+                       case FLAG_BASE:
+                       case FLAG_CARRY:
+                       {
+                               // do nothing for these flags
+                               break;
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       havocbot_ctf_reset_role(bot);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, TeamBalance_CheckAllowedTeams)
+{
+       M_ARGV(1, string) = "ctf_team";
+}
+
+MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
+{
+       entity spectatee = M_ARGV(0, entity);
+       entity client = M_ARGV(1, entity);
+
+       STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetRecords)
+{
+       int record_page = M_ARGV(0, int);
+       string ret_string = M_ARGV(1, string);
+
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if (MapInfo_Get_ByID(i))
+               {
+                       float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
+
+                       if(!r)
+                               continue;
+
+                       // TODO: uid2name
+                       string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
+               }
+       }
+
+       M_ARGV(1, string) = ret_string;
+}
+
+bool superspec_Spectate(entity this, entity targ); // TODO
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
+MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
+{
+       entity player = M_ARGV(0, entity);
+       string cmd_name = M_ARGV(1, string);
+       int cmd_argc = M_ARGV(2, int);
+
+       if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
+
+       if(cmd_name == "followfc")
+       {
+               if(!g_ctf)
+                       return true;
+
+               int _team = 0;
+               bool found = false;
+
+               if(cmd_argc == 2)
+               {
+                       switch(argv(1))
+                       {
+                               case "red":    if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
+                               case "blue":   if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
+                               case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
+                               case "pink":   if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
+                       }
+               }
+
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(it.flagcarried && (it.team == _team || _team == 0))
+                       {
+                               found = true;
+                               if(_team == 0 && IS_SPEC(player) && player.enemy == it)
+                                       continue; // already spectating this fc, try another
+                               return superspec_Spectate(player, it);
+                       }
+               });
+
+               if(!found)
+                       superspec_msg("", "", player, "No active flag carrier\n", 1);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
+{
+       entity frag_target = M_ARGV(0, entity);
+
+       if(frag_target.flagcarried)
+               ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
+}
+
+
+// ==========
+// Spawnfuncs
+// ==========
+
+/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team one (Red).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team1)
+{
+       if(!g_ctf) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_1, this);
+}
+
+/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team two (Blue).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team2)
+{
+       if(!g_ctf) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_2, this);
+}
+
+/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team three (Yellow).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team3)
+{
+       if(!g_ctf) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_3, this);
+}
+
+/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team four (Pink).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team4)
+{
+       if(!g_ctf) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_4, this);
+}
+
+/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag (Neutral).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_neutral)
+{
+       if(!g_ctf) { delete(this); return; }
+       if(!cvar("g_ctf_oneflag")) { delete(this); return; }
+
+       ctf_FlagSetup(0, this);
+}
+
+/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+spawnfunc(ctf_team)
+{
+       if(!g_ctf) { delete(this); return; }
+
+       this.classname = "ctf_team";
+       this.team = this.cnt + 1;
+}
+
+// compatibility for quake maps
+spawnfunc(team_CTF_redflag)    { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_CTF_blueflag)   { spawnfunc_item_flag_team2(this);    }
+spawnfunc(info_player_team1);
+spawnfunc(team_CTF_redplayer)  { spawnfunc_info_player_team1(this);  }
+spawnfunc(team_CTF_redspawn)   { spawnfunc_info_player_team1(this);  }
+spawnfunc(info_player_team2);
+spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this);  }
+spawnfunc(team_CTF_bluespawn)  { spawnfunc_info_player_team2(this);  }
+
+spawnfunc(team_CTF_neutralflag)        { spawnfunc_item_flag_neutral(this);  }
+spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this);  }
+
+// compatibility for wop maps
+spawnfunc(team_redplayer)      { spawnfunc_info_player_team1(this);  }
+spawnfunc(team_blueplayer)     { spawnfunc_info_player_team2(this);  }
+spawnfunc(team_ctl_redlolly)   { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_CTL_redlolly)   { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_ctl_bluelolly)  { spawnfunc_item_flag_team2(this);    }
+spawnfunc(team_CTL_bluelolly)  { spawnfunc_item_flag_team2(this);    }
+
+
+// ==============
+// Initialization
+// ==============
+
+// scoreboard setup
+void ctf_ScoreRules(int teams)
+{
+       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+        field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+        field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+        field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
+        field(SP_CTF_PICKUPS, "pickups", 0);
+        field(SP_CTF_FCKILLS, "fckills", 0);
+        field(SP_CTF_RETURNS, "returns", 0);
+        field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
+       });
+}
+
+// code from here on is just to support maps that don't have flag and team entities
+void ctf_SpawnTeam (string teamname, int teamcolor)
+{
+       entity this = new_pure(ctf_team);
+       this.netname = teamname;
+       this.cnt = teamcolor - 1;
+       this.spawnfunc_checked = true;
+       this.team = teamcolor;
+}
+
+void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+       ctf_teams = 0;
+
+       entity tmp_entity;
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       {
+               //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
+               //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+
+               switch(tmp_entity.team)
+               {
+                       case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
+                       case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
+                       case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
+                       case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
+               }
+               if(tmp_entity.team == 0) { ctf_oneflag = true; }
+       }
+
+       havocbot_ctf_calculate_middlepoint();
+
+       if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
+       {
+               ctf_teams = 0; // so set the default red and blue teams
+               BITSET_ASSIGN(ctf_teams, BIT(0));
+               BITSET_ASSIGN(ctf_teams, BIT(1));
+       }
+
+       //ctf_teams = bound(2, ctf_teams, 4);
+
+       // if no teams are found, spawn defaults
+       if(find(NULL, classname, "ctf_team") == NULL)
+       {
+               LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
+               if(ctf_teams & BIT(0))
+                       ctf_SpawnTeam("Red", NUM_TEAM_1);
+               if(ctf_teams & BIT(1))
+                       ctf_SpawnTeam("Blue", NUM_TEAM_2);
+               if(ctf_teams & BIT(2))
+                       ctf_SpawnTeam("Yellow", NUM_TEAM_3);
+               if(ctf_teams & BIT(3))
+                       ctf_SpawnTeam("Pink", NUM_TEAM_4);
+       }
+
+       ctf_ScoreRules(ctf_teams);
+}
+
+void ctf_Initialize()
+{
+       ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
+
+       ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
+       ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
+       ctf_captureshield_force = autocvar_g_ctf_shield_force;
+
+       InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qh b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qh
new file mode 100644 (file)
index 0000000..350ad8c
--- /dev/null
@@ -0,0 +1,169 @@
+#pragma once
+
+#include "ctf.qh"
+
+void ctf_Initialize();
+
+REGISTER_MUTATOR(ctf, false)
+{
+    MUTATOR_STATIC();
+    MUTATOR_ONADD
+    {
+        GameRules_teams(true);
+        GameRules_limit_score(autocvar_capturelimit_override);
+        GameRules_limit_lead(autocvar_captureleadlimit_override);
+
+        ctf_Initialize();
+    }
+    return 0;
+}
+
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag);
+
+// score rule declarations
+const int ST_CTF_CAPS = 1;
+
+CLASS(Flag, Pickup)
+    ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
+    ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
+ENDCLASS(Flag)
+Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
+void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
+
+// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
+
+const float FLAG_SCALE = 0.6;
+
+const float FLAG_THINKRATE = 0.2;
+const float FLAG_TOUCHRATE = 0.5;
+const float WPFE_THINKRATE = 0.5;
+
+const vector FLAG_DROP_OFFSET = ('0 0 32');
+const vector FLAG_CARRY_OFFSET = ('-16 0 8');
+#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
+const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
+const int FLAG_FLOAT_OFFSET_Z = 32;
+const int FLAG_PASS_ARC_OFFSET_Z = -10;
+
+const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
+const float VEHICLE_FLAG_SCALE = 1.0;
+
+// waypoint colors
+#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
+#define WPCOLOR_FLAGCARRIER(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
+//#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
+#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
+
+// sounds
+#define snd_flag_taken noise
+#define snd_flag_returned noise1
+#define snd_flag_capture noise2
+#define snd_flag_respawn noise3
+.string snd_flag_dropped;
+.string snd_flag_touch;
+.string snd_flag_pass;
+
+// score fields
+.float score_assist;
+.float score_capture;
+.float score_drop; // note: negated
+.float score_pickup;
+.float score_return;
+.float score_team_capture; // shouldn't be too high
+
+// effects
+.string toucheffect;
+.string passeffect;
+.string capeffect;
+
+// list of flags on the map
+entity ctf_worldflaglist;
+.entity ctf_worldflagnext;
+.entity ctf_staleflagnext;
+
+// waypoint sprites
+.entity wps_helpme;
+.entity wps_flagbase;
+.entity wps_flagcarrier;
+.entity wps_flagdropped;
+.entity wps_flagreturn;
+.entity wps_enemyflagcarrier;
+.float wps_helpme_time;
+bool wpforenemy_announced;
+float wpforenemy_nextthink;
+
+// statuses
+const int FLAG_BASE = 1;
+const int FLAG_DROPPED = 2;
+const int FLAG_CARRY = 3;
+const int FLAG_PASSING = 4;
+
+const int DROP_NORMAL = 1;
+const int DROP_THROW = 2;
+const int DROP_PASS = 3;
+const int DROP_RESET = 4;
+
+const int PICKUP_BASE = 1;
+const int PICKUP_DROPPED = 2;
+
+const int CAPTURE_NORMAL = 1;
+const int CAPTURE_DROPPED = 2;
+
+const int RETURN_TIMEOUT = 1;
+const int RETURN_DROPPED = 2;
+const int RETURN_DAMAGE = 3;
+const int RETURN_SPEEDRUN = 4;
+const int RETURN_NEEDKILL = 5;
+
+bool ctf_Stalemate_Customize(entity this, entity client);
+
+void ctf_Handle_Throw(entity player, entity receiver, float droptype);
+
+// flag properties
+#define ctf_spawnorigin dropped_origin
+bool ctf_stalemate; // indicates that a stalemate is active
+float ctf_captimerecord; // record time for capturing the flag
+.float ctf_pickuptime;
+.float ctf_droptime;
+.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
+.entity ctf_dropper; // don't allow spam of dropping the flag
+.int max_flag_health;
+.float next_take_time;
+.bool ctf_flagdamaged_byworld;
+int ctf_teams;
+.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
+
+// passing/throwing properties
+.float pass_distance;
+.entity pass_sender;
+.entity pass_target;
+.float throw_antispam;
+.float throw_prevtime;
+.int throw_count;
+
+// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
+.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
+float ctf_captureshield_min_negscore; // punish at -20 points
+float ctf_captureshield_max_ratio; // punish at most 30% of each team
+float ctf_captureshield_force; // push force of the shield
+
+// 1 flag ctf
+bool ctf_oneflag; // indicates whether or not a neutral flag has been found
+
+// bot player logic
+const int HAVOCBOT_CTF_ROLE_NONE = 0;
+const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
+const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
+const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
+const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
+const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
+const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
+
+.bool havocbot_cantfindflag;
+
+void havocbot_role_ctf_setrole(entity bot, int role);
+
+// team checking
+#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
+#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
index ab0d8a477450a1cb35957aeeea9c29903d64e81d..a48cd89ddad1053d7e198df6446bf25f8a114ed0 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/cts/cts.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/cts/sv_cts.qc>
+#endif
index a20b5c375b5716525db19d836d9f8a39abfb1bee..d05e62987fc613e48a9f253520b0de6f9fc0b9b0 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/cts/cts.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/cts/sv_cts.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qc b/qcsrc/common/gamemodes/gamemode/cts/cts.qc
deleted file mode 100644 (file)
index 6190289..0000000
+++ /dev/null
@@ -1,435 +0,0 @@
-#include "cts.qh"
-
-// TODO: split into sv_cts
-#ifdef SVQC
-#include <server/race.qh>
-#include <server/items.qh>
-
-float autocvar_g_cts_finish_kill_delay;
-bool autocvar_g_cts_selfdamage;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_cts(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               bool raw_touch_check = true;
-               int cp = this.race_checkpoint;
-
-               LABEL(search_racecheckpoints)
-               IL_EACH(g_racecheckpoints, true,
-               {
-                       if(it.cnt == cp || cp == -1)
-                       {
-                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
-                               // e.g. checkpoint in front of Stormkeep's warpzone
-                               // the same workaround is applied in Race game mode
-                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
-                               {
-                                       cp = race_NextCheckpoint(cp);
-                                       raw_touch_check = false;
-                                       goto search_racecheckpoints;
-                               }
-                               navigation_routerating(this, it, 1000000, 5000);
-                       }
-               });
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void cts_ScoreRules()
-{
-    GameRules_score_enabled(false);
-    GameRules_scoring(0, 0, 0, {
-        if (g_race_qualifying) {
-            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-        } else {
-            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
-        }
-    });
-}
-
-void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":cts:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void KillIndicator_Think(entity this);
-void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
-{
-    e.killindicator = spawn();
-    e.killindicator.owner = e;
-    setthink(e.killindicator, KillIndicator_Think);
-    e.killindicator.nextthink = time + (e.lip) * 0.05;
-    e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
-    e.killindicator.count = 1; // this is used to indicate that it should be silent
-    e.lip = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
-{
-       entity player = M_ARGV(0, entity);
-       float dt = M_ARGV(1, float);
-
-       player.race_movetime_frac += dt;
-       float f = floor(player.race_movetime_frac);
-       player.race_movetime_frac -= f;
-       player.race_movetime_count += f;
-       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
-       if(IS_PLAYER(player))
-       {
-               if (player.race_penalty)
-                       if (time > player.race_penalty)
-                               player.race_penalty = 0;
-               if(player.race_penalty)
-               {
-                       player.velocity = '0 0 0';
-                       set_movetype(player, MOVETYPE_NONE);
-                       player.disableclientprediction = 2;
-               }
-       }
-#endif
-
-       // force kbd movement for fairness
-       float wishspeed;
-       vector wishvel;
-
-       // if record times matter
-       // ensure nothing EVIL is being done (i.e. div0_evade)
-       // this hinders joystick users though
-       // but it still gives SOME analog control
-       wishvel.x = fabs(CS(player).movement.x);
-       wishvel.y = fabs(CS(player).movement.y);
-       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
-       {
-               wishvel.z = 0;
-               wishspeed = vlen(wishvel);
-               if(wishvel.x >= 2 * wishvel.y)
-               {
-                       // pure X motion
-                       if(CS(player).movement.x > 0)
-                               CS(player).movement_x = wishspeed;
-                       else
-                               CS(player).movement_x = -wishspeed;
-                       CS(player).movement_y = 0;
-               }
-               else if(wishvel.y >= 2 * wishvel.x)
-               {
-                       // pure Y motion
-                       CS(player).movement_x = 0;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = wishspeed;
-                       else
-                               CS(player).movement_y = -wishspeed;
-               }
-               else
-               {
-                       // diagonal
-                       if(CS(player).movement.x > 0)
-                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, reset_map_global)
-{
-       float s;
-
-       Score_NicePrint(NULL);
-
-       race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
-
-       FOREACH_CLIENT(true, {
-               if(it.race_place)
-               {
-                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
-                       if(!s)
-                               it.race_place = 0;
-               }
-               cts_EventLog(ftos(it.race_place), it);
-       });
-
-       if(g_race_qualifying == 2)
-       {
-               g_race_qualifying = 0;
-               independent_players = 0;
-               cvar_set("fraglimit", ftos(race_fraglimit));
-               cvar_set("leadlimit", ftos(race_leadlimit));
-               cvar_set("timelimit", ftos(race_timelimit));
-               cts_ScoreRules();
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-
-       if(IS_REAL_CLIENT(player))
-       {
-               string rr = CTS_RECORD;
-
-               msg_entity = player;
-               race_send_recordtime(MSG_ONE);
-               race_send_speedaward(MSG_ONE);
-
-               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
-               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
-               race_send_speedaward_alltimebest(MSG_ONE);
-
-               float i;
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(autocvar_g_allow_checkpoints)
-               race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
-               player.frags = FRAGS_LMS_LOSER;
-       else
-               player.frags = FRAGS_SPECTATOR;
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-       entity spawn_spot = M_ARGV(1, entity);
-
-       if(spawn_spot.target == "")
-               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
-               race_PreparePlayer(player);
-
-       // if we need to respawn, do it right
-       player.race_respawn_checkpoint = player.race_checkpoint;
-       player.race_respawn_spotref = spawn_spot;
-
-       player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(IS_PLAYER(player))
-       if(!game_stopped)
-       {
-               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
-                       race_PreparePlayer(player);
-               else // respawn
-                       race_RetractPlayer(player);
-
-               race_AbandonRaceCheck(player);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-       race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       bot.havocbot_role = havocbot_role_cts;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-
-       if (!IS_OBSERVER(player))
-       {
-               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
-               {
-                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
-                       speedaward_holder = player.netname;
-                       speedaward_uid = player.crypto_idfp;
-                       speedaward_lastupdate = time;
-               }
-               if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
-               {
-                       string rr = CTS_RECORD;
-                       race_send_speedaward(MSG_ALL);
-                       speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
-                       {
-                               speedaward_alltimebest = speedaward_speed;
-                               speedaward_alltimebest_holder = speedaward_holder;
-                               speedaward_alltimebest_uid = speedaward_uid;
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
-                               race_send_speedaward_alltimebest(MSG_ALL);
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
-{
-       // no weapon dropping in CTS
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, FilterItem)
-{
-       entity item = M_ARGV(0, entity);
-
-       if (Item_IsLoot(item))
-       {
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_deathtype = M_ARGV(3, float);
-       float frag_damage = M_ARGV(4, float);
-
-       if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
-       if(!autocvar_g_cts_selfdamage)
-       {
-               frag_damage = 0;
-               M_ARGV(4, float) = frag_damage;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
-{
-       return true; // in CTS, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetRecords)
-{
-       int record_page = M_ARGV(0, int);
-       string ret_string = M_ARGV(1, string);
-
-       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
-       {
-               if(MapInfo_Get_ByID(i))
-               {
-                       float r = race_readTime(MapInfo_Map_bspname, 1);
-
-                       if(!r)
-                               continue;
-
-                       string h = race_readName(MapInfo_Map_bspname, 1);
-                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
-               }
-       }
-
-       M_ARGV(1, string) = ret_string;
-}
-
-void ClientKill_Now(entity this);
-MUTATOR_HOOKFUNCTION(cts, ClientKill)
-{
-    entity player = M_ARGV(0, entity);
-
-       M_ARGV(1, float) = 0; // kill delay
-
-       if(player.killindicator && player.killindicator.count == 1) // player.killindicator.count == 1 means that the kill indicator was spawned by CTS_ClientKill
-       {
-               delete(player.killindicator);
-               player.killindicator = NULL;
-
-               ClientKill_Now(player); // allow instant kill in this case
-               return;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(autocvar_g_cts_finish_kill_delay)
-               CTS_ClientKill(player);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
-{
-       return true; // doesn't work so well (but isn't cts a teamless mode?)
-}
-
-MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
-{
-       entity player = M_ARGV(0, entity);
-
-       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(cts, WantWeapon)
-{
-       M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
-       M_ARGV(3, bool) = true; // want mutator blocked
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
-{
-       return true;
-}
-
-void cts_Initialize()
-{
-       cts_ScoreRules();
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qh b/qcsrc/common/gamemodes/gamemode/cts/cts.qh
deleted file mode 100644 (file)
index 516e903..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <server/race.qh>
-
-void cts_Initialize();
-
-REGISTER_MUTATOR(cts, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               g_race_qualifying = true;
-               independent_players = 1;
-        GameRules_limit_score(0);
-        GameRules_limit_lead(0);
-
-               cts_Initialize();
-       }
-       return 0;
-}
-
-// scores
-const float ST_CTS_LAPS = 1;
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc b/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc
new file mode 100644 (file)
index 0000000..31bdae0
--- /dev/null
@@ -0,0 +1,416 @@
+#include "sv_cts.qh"
+
+#include <server/race.qh>
+#include <server/items.qh>
+
+float autocvar_g_cts_finish_kill_delay;
+bool autocvar_g_cts_selfdamage;
+bool autocvar_g_cts_removeprojectiles;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_cts(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
+               IL_EACH(g_racecheckpoints, true,
+               {
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in Race game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
+                               navigation_routerating(this, it, 1000000, 5000);
+                       }
+               });
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void cts_ScoreRules()
+{
+    GameRules_score_enabled(false);
+    GameRules_scoring(0, 0, 0, {
+        if (g_race_qualifying) {
+            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+        } else {
+            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+        }
+    });
+}
+
+void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":cts:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
+{
+       entity player = M_ARGV(0, entity);
+       float dt = M_ARGV(1, float);
+
+       player.race_movetime_frac += dt;
+       float f = floor(player.race_movetime_frac);
+       player.race_movetime_frac -= f;
+       player.race_movetime_count += f;
+       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+       if(IS_PLAYER(player))
+       {
+               if (player.race_penalty)
+                       if (time > player.race_penalty)
+                               player.race_penalty = 0;
+               if(player.race_penalty)
+               {
+                       player.velocity = '0 0 0';
+                       set_movetype(player, MOVETYPE_NONE);
+                       player.disableclientprediction = 2;
+               }
+       }
+
+       // force kbd movement for fairness
+       float wishspeed;
+       vector wishvel;
+
+       // if record times matter
+       // ensure nothing EVIL is being done (i.e. div0_evade)
+       // this hinders joystick users though
+       // but it still gives SOME analog control
+       wishvel.x = fabs(CS(player).movement.x);
+       wishvel.y = fabs(CS(player).movement.y);
+       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
+       {
+               wishvel.z = 0;
+               wishspeed = vlen(wishvel);
+               if(wishvel.x >= 2 * wishvel.y)
+               {
+                       // pure X motion
+                       if(CS(player).movement.x > 0)
+                               CS(player).movement_x = wishspeed;
+                       else
+                               CS(player).movement_x = -wishspeed;
+                       CS(player).movement_y = 0;
+               }
+               else if(wishvel.y >= 2 * wishvel.x)
+               {
+                       // pure Y motion
+                       CS(player).movement_x = 0;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = wishspeed;
+                       else
+                               CS(player).movement_y = -wishspeed;
+               }
+               else
+               {
+                       // diagonal
+                       if(CS(player).movement.x > 0)
+                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, reset_map_global)
+{
+       float s;
+
+       Score_NicePrint(NULL);
+
+       race_ClearRecords();
+       PlayerScore_Sort(race_place, 0, 1, 0);
+
+       FOREACH_CLIENT(true, {
+               if(it.race_place)
+               {
+                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+                       if(!s)
+                               it.race_place = 0;
+               }
+               cts_EventLog(ftos(it.race_place), it);
+       });
+
+       if(g_race_qualifying == 2)
+       {
+               g_race_qualifying = 0;
+               independent_players = 0;
+               cvar_set("fraglimit", ftos(race_fraglimit));
+               cvar_set("leadlimit", ftos(race_leadlimit));
+               cvar_set("timelimit", ftos(race_timelimit));
+               cts_ScoreRules();
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+
+       if(IS_REAL_CLIENT(player))
+       {
+               string rr = CTS_RECORD;
+
+               msg_entity = player;
+               race_send_recordtime(MSG_ONE);
+               race_send_speedaward(MSG_ONE);
+
+               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
+               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
+               race_send_speedaward_alltimebest(MSG_ONE);
+
+               float i;
+               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+               race_send_rankings_cnt(MSG_ONE);
+               for (i = 1; i <= m; ++i)
+               {
+                       race_SendRankings(i, 0, 0, MSG_ONE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(autocvar_g_allow_checkpoints)
+               race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+               player.frags = FRAGS_LMS_LOSER;
+       else
+               player.frags = FRAGS_SPECTATOR;
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       entity spawn_spot = M_ARGV(1, entity);
+
+       if(spawn_spot.target == "")
+               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+               race_PreparePlayer(player);
+
+       // if we need to respawn, do it right
+       player.race_respawn_checkpoint = player.race_checkpoint;
+       player.race_respawn_spotref = spawn_spot;
+
+       player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(IS_PLAYER(player))
+       if(!game_stopped)
+       {
+               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+                       race_PreparePlayer(player);
+               else // respawn
+                       race_RetractPlayer(player);
+
+               race_AbandonRaceCheck(player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       race_AbandonRaceCheck(frag_target);
+
+       if(autocvar_g_cts_removeprojectiles)
+       {
+               IL_EACH(g_projectiles, it.owner == frag_target && (it.flags & FL_PROJECTILE),
+               {
+                       delete(it);
+               });
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       bot.havocbot_role = havocbot_role_cts;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+       {
+               if (!player.stored_netname)
+                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
+               if(player.stored_netname != player.netname)
+               {
+                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+                       strcpy(player.stored_netname, player.netname);
+               }
+       }
+
+       if (!IS_OBSERVER(player))
+       {
+               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+               {
+                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+                       speedaward_holder = player.netname;
+                       speedaward_uid = player.crypto_idfp;
+                       speedaward_lastupdate = time;
+               }
+               if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+               {
+                       string rr = CTS_RECORD;
+                       race_send_speedaward(MSG_ALL);
+                       speedaward_lastsent = speedaward_speed;
+                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+                       {
+                               speedaward_alltimebest = speedaward_speed;
+                               speedaward_alltimebest_holder = speedaward_holder;
+                               speedaward_alltimebest_uid = speedaward_uid;
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+                               race_send_speedaward_alltimebest(MSG_ALL);
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
+{
+       // no weapon dropping in CTS
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, FilterItem)
+{
+       entity item = M_ARGV(0, entity);
+
+       if (Item_IsLoot(item))
+       {
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+       float frag_damage = M_ARGV(4, float);
+
+       if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
+       if(!autocvar_g_cts_selfdamage)
+       {
+               frag_damage = 0;
+               M_ARGV(4, float) = frag_damage;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
+{
+       return true; // in CTS, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetRecords)
+{
+       int record_page = M_ARGV(0, int);
+       string ret_string = M_ARGV(1, string);
+
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if(MapInfo_Get_ByID(i))
+               {
+                       float r = race_readTime(MapInfo_Map_bspname, 1);
+
+                       if(!r)
+                               continue;
+
+                       string h = race_readName(MapInfo_Map_bspname, 1);
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
+               }
+       }
+
+       M_ARGV(1, string) = ret_string;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientKill)
+{
+       M_ARGV(1, float) = 0; // kill delay
+}
+
+MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
+{
+       entity player = M_ARGV(0, entity);
+
+       // useful to prevent cheating by running back to the start line and starting out with more speed
+       if(autocvar_g_cts_finish_kill_delay)
+               ClientKill_Silent(player, autocvar_g_cts_finish_kill_delay);
+}
+
+MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
+{
+       return true; // doesn't work so well (but isn't cts a teamless mode?)
+}
+
+MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
+{
+       entity player = M_ARGV(0, entity);
+
+       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(cts, WantWeapon)
+{
+       M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
+       M_ARGV(3, bool) = true; // want mutator blocked
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
+{
+       return true;
+}
+
+void cts_Initialize()
+{
+       cts_ScoreRules();
+}
diff --git a/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qh b/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qh
new file mode 100644 (file)
index 0000000..8c8453d
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <server/race.qh>
+
+void cts_Initialize();
+
+REGISTER_MUTATOR(cts, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               g_race_qualifying = true;
+               independent_players = 1;
+        GameRules_limit_score(0);
+        GameRules_limit_lead(0);
+
+               cts_Initialize();
+       }
+       return 0;
+}
+
+// scores
+const float ST_CTS_LAPS = 1;
index 2403aad758dc130f3aea6fd8dd40939bc302ecea..ba2449386cd20b9de4795a5ced19e00f44be1b07 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/deathmatch/deathmatch.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/deathmatch/sv_deathmatch.qc>
+#endif
index 2135ec9d8271f1e3e1c35e528f13632b4c4ed5a8..abc7db377aba51025c210a7a9d40329dafad65a6 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/deathmatch/deathmatch.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/deathmatch/sv_deathmatch.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc
deleted file mode 100644 (file)
index 5cd7ca1..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "deathmatch.qh"
-
-// TODO: sv_deathmatch?
-#ifdef SVQC
-MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh
deleted file mode 100644 (file)
index fdae278..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-REGISTER_MUTATOR(dm, false)
-{
-    MUTATOR_STATIC();
-       return 0;
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/sv_deathmatch.qc b/qcsrc/common/gamemodes/gamemode/deathmatch/sv_deathmatch.qc
new file mode 100644 (file)
index 0000000..e622a19
--- /dev/null
@@ -0,0 +1,7 @@
+#include "sv_deathmatch.qh"
+
+MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/sv_deathmatch.qh b/qcsrc/common/gamemodes/gamemode/deathmatch/sv_deathmatch.qh
new file mode 100644 (file)
index 0000000..c08d5f8
--- /dev/null
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+REGISTER_MUTATOR(dm, false)
+{
+    MUTATOR_STATIC();
+       return 0;
+}
index 95d00b3891afe1a1c970594b1fde0dd910a709c7..ff9bc11f881d0afb4031fd3dd0937f37f2093f93 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/domination/domination.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/domination/sv_domination.qc>
+#endif
index e57c30efecf1e072bba3433c5e8911bad3dce747..0c5e8412146e62d3970d3452f98d25c5f60af386 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/domination/domination.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/domination/sv_domination.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qc b/qcsrc/common/gamemodes/gamemode/domination/domination.qc
deleted file mode 100644 (file)
index 64ff123..0000000
+++ /dev/null
@@ -1,673 +0,0 @@
-#include "domination.qh"
-
-// TODO: sv_domination
-#ifdef SVQC
-#include <server/teamplay.qh>
-
-bool g_domination;
-
-int autocvar_g_domination_default_teams;
-bool autocvar_g_domination_disable_frags;
-int autocvar_g_domination_point_amt;
-bool autocvar_g_domination_point_fullbright;
-float autocvar_g_domination_round_timelimit;
-float autocvar_g_domination_warmup;
-float autocvar_g_domination_point_rate;
-int autocvar_g_domination_teams_override;
-
-void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void set_dom_state(entity e)
-{
-       STAT(DOM_TOTAL_PPS, e) = total_pps;
-       STAT(DOM_PPS_RED, e) = pps_red;
-       STAT(DOM_PPS_BLUE, e) = pps_blue;
-       if(domination_teams >= 3)
-               STAT(DOM_PPS_YELLOW, e) = pps_yellow;
-       if(domination_teams >= 4)
-               STAT(DOM_PPS_PINK, e) = pps_pink;
-}
-
-void dompoint_captured(entity this)
-{
-       float old_delay, old_team, real_team;
-
-       // now that the delay has expired, switch to the latest team to lay claim to this point
-       entity head = this.owner;
-
-       real_team = this.cnt;
-       this.cnt = -1;
-
-       dom_EventLog("taken", this.team, this.dmg_inflictor);
-       this.dmg_inflictor = NULL;
-
-       this.goalentity = head;
-       this.model = head.mdl;
-       this.modelindex = head.dmg;
-       this.skin = head.skin;
-
-       float points, wait_time;
-       if (autocvar_g_domination_point_amt)
-               points = autocvar_g_domination_point_amt;
-       else
-               points = this.frags;
-       if (autocvar_g_domination_point_rate)
-               wait_time = autocvar_g_domination_point_rate;
-       else
-               wait_time = this.wait;
-
-       if(domination_roundbased)
-               bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
-
-       if(this.enemy.playerid == this.enemy_playerid)
-               GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
-       else
-               this.enemy = NULL;
-
-       if (head.noise != "")
-               if(this.enemy)
-                       _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
-               else
-                       _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
-       if (head.noise1 != "")
-               play2all(head.noise1);
-
-       this.delay = time + wait_time;
-
-       // do trigger work
-       old_delay = this.delay;
-       old_team = this.team;
-       this.team = real_team;
-       this.delay = 0;
-       SUB_UseTargets (this, this, NULL);
-       this.delay = old_delay;
-       this.team = old_team;
-
-       entity msg = WP_DomNeut;
-       switch(real_team)
-       {
-               case NUM_TEAM_1: msg = WP_DomRed; break;
-               case NUM_TEAM_2: msg = WP_DomBlue; break;
-               case NUM_TEAM_3: msg = WP_DomYellow; break;
-               case NUM_TEAM_4: msg = WP_DomPink; break;
-       }
-
-       WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
-
-       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       IL_EACH(g_dompoints, true,
-       {
-               if (autocvar_g_domination_point_amt)
-                       points = autocvar_g_domination_point_amt;
-               else
-                       points = it.frags;
-               if (autocvar_g_domination_point_rate)
-                       wait_time = autocvar_g_domination_point_rate;
-               else
-                       wait_time = it.wait;
-               switch(it.goalentity.team)
-               {
-                       case NUM_TEAM_1: pps_red += points/wait_time; break;
-                       case NUM_TEAM_2: pps_blue += points/wait_time; break;
-                       case NUM_TEAM_3: pps_yellow += points/wait_time; break;
-                       case NUM_TEAM_4: pps_pink += points/wait_time; break;
-               }
-               total_pps += points/wait_time;
-       });
-
-       WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
-       WaypointSprite_Ping(this.sprite);
-
-       this.captime = time;
-
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
-}
-
-void AnimateDomPoint(entity this)
-{
-       if(this.pain_finished > time)
-               return;
-       this.pain_finished = time + this.t_width;
-       if(this.nextthink > this.pain_finished)
-               this.nextthink = this.pain_finished;
-
-       this.frame = this.frame + 1;
-       if(this.frame > this.t_length)
-               this.frame = 0;
-}
-
-void dompointthink(entity this)
-{
-       float fragamt;
-
-       this.nextthink = time + 0.1;
-
-       //this.frame = this.frame + 1;
-       //if(this.frame > 119)
-       //      this.frame = 0;
-       AnimateDomPoint(this);
-
-       // give points
-
-       if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
-               return;
-
-       if(autocvar_g_domination_point_rate)
-               this.delay = time + autocvar_g_domination_point_rate;
-       else
-               this.delay = time + this.wait;
-
-       // give credit to the team
-       // NOTE: this defaults to 0
-       if (!domination_roundbased)
-       if (this.goalentity.netname != "")
-       {
-               if(autocvar_g_domination_point_amt)
-                       fragamt = autocvar_g_domination_point_amt;
-               else
-                       fragamt = this.frags;
-               TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
-               TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
-
-               // give credit to the individual player, if he is still there
-               if (this.enemy.playerid == this.enemy_playerid)
-               {
-                       GameRules_scoring_add(this.enemy, SCORE, fragamt);
-                       GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
-               }
-               else
-                       this.enemy = NULL;
-       }
-}
-
-void dompointtouch(entity this, entity toucher)
-{
-       if(!IS_PLAYER(toucher))
-               return;
-       if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
-               return;
-
-       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
-               return;
-
-       if(time < this.captime + 0.3)
-               return;
-
-       // only valid teams can claim it
-       entity head = find(NULL, classname, "dom_team");
-       while (head && head.team != toucher.team)
-               head = find(head, classname, "dom_team");
-       if (!head || head.netname == "" || head == this.goalentity)
-               return;
-
-       // delay capture
-
-       this.team = this.goalentity.team; // this stores the PREVIOUS team!
-
-       this.cnt = toucher.team;
-       this.owner = head; // team to switch to after the delay
-       this.dmg_inflictor = toucher;
-
-       // this.state = 1;
-       // this.delay = time + cvar("g_domination_point_capturetime");
-       //this.nextthink = time + cvar("g_domination_point_capturetime");
-       //this.think = dompoint_captured;
-
-       // go to neutral team in the mean time
-       head = find(NULL, classname, "dom_team");
-       while (head && head.netname != "")
-               head = find(head, classname, "dom_team");
-       if(head == NULL)
-               return;
-
-       WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
-       WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
-       WaypointSprite_Ping(this.sprite);
-
-       this.goalentity = head;
-       this.model = head.mdl;
-       this.modelindex = head.dmg;
-       this.skin = head.skin;
-
-       this.enemy = toucher; // individual player scoring
-       this.enemy_playerid = toucher.playerid;
-       dompoint_captured(this);
-}
-
-void dom_controlpoint_setup(entity this)
-{
-       entity head;
-       // find the spawnfunc_dom_team representing unclaimed points
-       head = find(NULL, classname, "dom_team");
-       while(head && head.netname != "")
-               head = find(head, classname, "dom_team");
-       if (!head)
-               objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
-
-       // copy important properties from spawnfunc_dom_team entity
-       this.goalentity = head;
-       _setmodel(this, head.mdl); // precision already set
-       this.skin = head.skin;
-
-       this.cnt = -1;
-
-       if(this.message == "")
-               this.message = " has captured a control point";
-
-       if(this.frags <= 0)
-               this.frags = 1;
-       if(this.wait <= 0)
-               this.wait = 5;
-
-       float points, waittime;
-       if (autocvar_g_domination_point_amt)
-               points = autocvar_g_domination_point_amt;
-       else
-               points = this.frags;
-       if (autocvar_g_domination_point_rate)
-               waittime = autocvar_g_domination_point_rate;
-       else
-               waittime = this.wait;
-
-       total_pps += points/waittime;
-
-       if(!this.t_width)
-               this.t_width = 0.02; // frame animation rate
-       if(!this.t_length)
-               this.t_length = 239; // maximum frame
-
-       setthink(this, dompointthink);
-       this.nextthink = time;
-       settouch(this, dompointtouch);
-       this.solid = SOLID_TRIGGER;
-       if(!this.flags & FL_ITEM)
-               IL_PUSH(g_items, this);
-       this.flags = FL_ITEM;
-       setsize(this, '-32 -32 -32', '32 32 32');
-       setorigin(this, this.origin + '0 0 20');
-       droptofloor(this);
-
-       waypoint_spawnforitem(this);
-       WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
-}
-
-float total_controlpoints;
-void Domination_count_controlpoints()
-{
-       total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
-       IL_EACH(g_dompoints, true,
-       {
-               ++total_controlpoints;
-               redowned += (it.goalentity.team == NUM_TEAM_1);
-               blueowned += (it.goalentity.team == NUM_TEAM_2);
-               yellowowned += (it.goalentity.team == NUM_TEAM_3);
-               pinkowned += (it.goalentity.team == NUM_TEAM_4);
-       });
-}
-
-float Domination_GetWinnerTeam()
-{
-       float winner_team = 0;
-       if(redowned == total_controlpoints)
-               winner_team = NUM_TEAM_1;
-       if(blueowned == total_controlpoints)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowowned == total_controlpoints)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkowned == total_controlpoints)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no control points left?
-}
-
-#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
-float Domination_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-
-               game_stopped = true;
-               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-               return 1;
-       }
-
-       Domination_count_controlpoints();
-
-       float winner_team = Domination_GetWinnerTeam();
-
-       if(winner_team == -1)
-               return 0;
-
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       game_stopped = true;
-       round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-
-       return 1;
-}
-
-float Domination_CheckPlayers()
-{
-       return 1;
-}
-
-void Domination_RoundStart()
-{
-       FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
-}
-
-//go to best items, or control points you don't own
-void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
-{
-       IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
-       {
-               if(it.cnt > -1) // this is just being fought
-                       navigation_routerating(this, it, ratingscale, 5000);
-               else if(it.goalentity.cnt == 0) // unclaimed
-                       navigation_routerating(this, it, ratingscale * 0.5, 5000);
-               else if(it.goalentity.team != this.team) // other team's point
-                       navigation_routerating(this, it, ratingscale * 0.2, 5000);
-       });
-}
-
-void havocbot_role_dom(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
-               havocbot_goalrating_items(this, 8000, this.origin, 8000);
-               //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
-{
-       // fallback?
-       M_ARGV(0, float) = domination_teams;
-       string ret_string = "dom_team";
-
-       entity head = find(NULL, classname, ret_string);
-       while(head)
-       {
-               if(head.netname != "")
-               {
-                       switch(head.team)
-                       {
-                               case NUM_TEAM_1: c1 = 0; break;
-                               case NUM_TEAM_2: c2 = 0; break;
-                               case NUM_TEAM_3: c3 = 0; break;
-                               case NUM_TEAM_4: c4 = 0; break;
-                       }
-               }
-
-               head = find(head, classname, ret_string);
-       }
-
-       M_ARGV(1, string) = string_null;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, reset_map_players)
-{
-       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               PutClientInServer(it);
-               if(domination_roundbased)
-                       it.player_blocked = 1;
-               if(IS_REAL_CLIENT(it))
-                       set_dom_state(it);
-       });
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(domination_roundbased)
-       if(!round_handler_IsRoundStarted())
-               player.player_blocked = 1;
-       else
-               player.player_blocked = 0;
-}
-
-MUTATOR_HOOKFUNCTION(dom, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       set_dom_state(player);
-}
-
-MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       bot.havocbot_role = havocbot_role_dom;
-       return true;
-}
-
-/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
-Control point for Domination gameplay.
-*/
-spawnfunc(dom_controlpoint)
-{
-       if(!g_domination)
-       {
-               delete(this);
-               return;
-       }
-       setthink(this, dom_controlpoint_setup);
-       this.nextthink = time + 0.1;
-       this.reset = dom_controlpoint_setup;
-
-       if(!this.scale)
-               this.scale = 0.6;
-
-       this.effects = this.effects | EF_LOWPRECISION;
-       if (autocvar_g_domination_point_fullbright)
-               this.effects |= EF_FULLBRIGHT;
-
-       IL_PUSH(g_dompoints, this);
-}
-
-/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
-Team declaration for Domination gameplay, this allows you to decide what team
-names and control point models are used in your map.
-
-Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
-can have netname set!  The nameless team owns all control points at start.
-
-Keys:
-"netname"
- Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
-"cnt"
- Scoreboard color of the team (for example 4 is red and 13 is blue)
-"model"
- Model to use for control points owned by this team (for example
- "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
- keycard)
-"skin"
- Skin of the model to use (for team skins on a single model)
-"noise"
- Sound to play when this team captures a point.
- (this is a localized sound, like a small alarm or other effect)
-"noise1"
- Narrator speech to play when this team captures a point.
- (this is a global sound, like "Red team has captured a control point")
-*/
-
-spawnfunc(dom_team)
-{
-       if(!g_domination || autocvar_g_domination_teams_override >= 2)
-       {
-               delete(this);
-               return;
-       }
-       precache_model(this.model);
-       if (this.noise != "")
-               precache_sound(this.noise);
-       if (this.noise1 != "")
-               precache_sound(this.noise1);
-       this.classname = "dom_team";
-       _setmodel(this, this.model); // precision not needed
-       this.mdl = this.model;
-       this.dmg = this.modelindex;
-       this.model = "";
-       this.modelindex = 0;
-       // this would have to be changed if used in quakeworld
-       if(this.cnt)
-               this.team = this.cnt + 1; // WHY are these different anyway?
-}
-
-// scoreboard setup
-void ScoreRules_dom(int teams)
-{
-       if(domination_roundbased)
-       {
-           GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
-            field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_DOM_TAKES, "takes", 0);
-           });
-       }
-       else
-       {
-               float sp_domticks, sp_score;
-               sp_score = sp_domticks = 0;
-               if(autocvar_g_domination_disable_frags)
-                       sp_domticks = SFL_SORT_PRIO_PRIMARY;
-               else
-                       sp_score = SFL_SORT_PRIO_PRIMARY;
-               GameRules_scoring(teams, sp_score, sp_score, {
-            field_team(ST_DOM_TICKS, "ticks", sp_domticks);
-            field(SP_DOM_TICKS, "ticks", sp_domticks);
-            field(SP_DOM_TAKES, "takes", 0);
-               });
-       }
-}
-
-// code from here on is just to support maps that don't have control point and team entities
-void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
-{
-       TC(Sound, capsound);
-       entity e = new_pure(dom_team);
-       e.netname = strzone(teamname);
-       e.cnt = teamcolor;
-       e.model = pointmodel;
-       e.skin = pointskin;
-       e.noise = strzone(Sound_fixpath(capsound));
-       e.noise1 = strzone(capnarration);
-       e.message = strzone(capmessage);
-
-       // this code is identical to spawnfunc_dom_team
-       _setmodel(e, e.model); // precision not needed
-       e.mdl = e.model;
-       e.dmg = e.modelindex;
-       e.model = "";
-       e.modelindex = 0;
-       // this would have to be changed if used in quakeworld
-       e.team = e.cnt + 1;
-
-       //eprint(e);
-}
-
-void dom_spawnpoint(vector org)
-{
-       entity e = spawn();
-       e.classname = "dom_controlpoint";
-       setthink(e, spawnfunc_dom_controlpoint);
-       e.nextthink = time;
-       setorigin(e, org);
-       spawnfunc_dom_controlpoint(e);
-}
-
-// spawn some default teams if the map is not set up for domination
-void dom_spawnteams(int teams)
-{
-       TC(int, teams);
-       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
-       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point");
-       if(teams >= 3)
-               dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point");
-       if(teams >= 4)
-               dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point");
-       dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
-}
-
-void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
-       // if no teams are found, spawn defaults
-       if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
-       {
-               LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
-               domination_teams = autocvar_g_domination_teams_override;
-               if (domination_teams < 2)
-                       domination_teams = autocvar_g_domination_default_teams;
-               domination_teams = bound(2, domination_teams, 4);
-               dom_spawnteams(domination_teams);
-       }
-
-       CheckAllowedTeams(NULL);
-       //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
-
-       int teams = 0;
-       if(c1 >= 0) teams |= BIT(0);
-       if(c2 >= 0) teams |= BIT(1);
-       if(c3 >= 0) teams |= BIT(2);
-       if(c4 >= 0) teams |= BIT(3);
-       domination_teams = teams;
-
-       domination_roundbased = autocvar_g_domination_roundbased;
-
-       ScoreRules_dom(domination_teams);
-
-       if(domination_roundbased)
-       {
-               round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
-               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-       }
-}
-
-void dom_Initialize()
-{
-       g_domination = true;
-       InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qh b/qcsrc/common/gamemodes/gamemode/domination/domination.qh
deleted file mode 100644 (file)
index f4faf50..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
-bool autocvar_g_domination_roundbased;
-int autocvar_g_domination_roundbased_point_limit;
-int autocvar_g_domination_point_leadlimit;
-
-void dom_Initialize();
-
-REGISTER_MUTATOR(dom, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               int fraglimit_override = autocvar_g_domination_point_limit;
-               if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
-                       fraglimit_override = autocvar_g_domination_roundbased_point_limit;
-
-               GameRules_teams(true);
-        GameRules_limit_score(fraglimit_override);
-        GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
-
-               dom_Initialize();
-       }
-       return 0;
-}
-
-// score rule declarations
-const float ST_DOM_TICKS = 1;
-const float ST_DOM_CAPS = 1;
-
-// pps: points per second
-float total_pps;
-float pps_red;
-float pps_blue;
-float pps_yellow;
-float pps_pink;
-
-// capture declarations
-.float enemy_playerid;
-.entity sprite;
-.float captime;
-
-// misc globals
-float domination_roundbased;
-float domination_teams;
-
-void AnimateDomPoint(entity this);
-
-IntrusiveList g_dompoints;
-STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc b/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc
new file mode 100644 (file)
index 0000000..78ff64e
--- /dev/null
@@ -0,0 +1,670 @@
+#include "sv_domination.qh"
+
+#include <server/teamplay.qh>
+
+bool g_domination;
+
+int autocvar_g_domination_default_teams;
+bool autocvar_g_domination_disable_frags;
+int autocvar_g_domination_point_amt;
+bool autocvar_g_domination_point_fullbright;
+float autocvar_g_domination_round_timelimit;
+float autocvar_g_domination_warmup;
+float autocvar_g_domination_point_rate;
+int autocvar_g_domination_teams_override;
+
+void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void set_dom_state(entity e)
+{
+       STAT(DOM_TOTAL_PPS, e) = total_pps;
+       STAT(DOM_PPS_RED, e) = pps_red;
+       STAT(DOM_PPS_BLUE, e) = pps_blue;
+       if(domination_teams >= 3)
+               STAT(DOM_PPS_YELLOW, e) = pps_yellow;
+       if(domination_teams >= 4)
+               STAT(DOM_PPS_PINK, e) = pps_pink;
+}
+
+void dompoint_captured(entity this)
+{
+       float old_delay, old_team, real_team;
+
+       // now that the delay has expired, switch to the latest team to lay claim to this point
+       entity head = this.owner;
+
+       real_team = this.cnt;
+       this.cnt = -1;
+
+       dom_EventLog("taken", this.team, this.dmg_inflictor);
+       this.dmg_inflictor = NULL;
+
+       this.goalentity = head;
+       this.model = head.mdl;
+       this.modelindex = head.dmg;
+       this.skin = head.skin;
+
+       float points, wait_time;
+       if (autocvar_g_domination_point_amt)
+               points = autocvar_g_domination_point_amt;
+       else
+               points = this.frags;
+       if (autocvar_g_domination_point_rate)
+               wait_time = autocvar_g_domination_point_rate;
+       else
+               wait_time = this.wait;
+
+       if(domination_roundbased)
+               bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
+       else
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
+
+       if(this.enemy.playerid == this.enemy_playerid)
+               GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
+       else
+               this.enemy = NULL;
+
+       if (head.noise != "")
+               if(this.enemy)
+                       _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+               else
+                       _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+       if (head.noise1 != "")
+               play2all(head.noise1);
+
+       this.delay = time + wait_time;
+
+       // do trigger work
+       old_delay = this.delay;
+       old_team = this.team;
+       this.team = real_team;
+       this.delay = 0;
+       SUB_UseTargets (this, this, NULL);
+       this.delay = old_delay;
+       this.team = old_team;
+
+       entity msg = WP_DomNeut;
+       switch(real_team)
+       {
+               case NUM_TEAM_1: msg = WP_DomRed; break;
+               case NUM_TEAM_2: msg = WP_DomBlue; break;
+               case NUM_TEAM_3: msg = WP_DomYellow; break;
+               case NUM_TEAM_4: msg = WP_DomPink; break;
+       }
+
+       WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
+
+       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+       IL_EACH(g_dompoints, true,
+       {
+               if (autocvar_g_domination_point_amt)
+                       points = autocvar_g_domination_point_amt;
+               else
+                       points = it.frags;
+               if (autocvar_g_domination_point_rate)
+                       wait_time = autocvar_g_domination_point_rate;
+               else
+                       wait_time = it.wait;
+               switch(it.goalentity.team)
+               {
+                       case NUM_TEAM_1: pps_red += points/wait_time; break;
+                       case NUM_TEAM_2: pps_blue += points/wait_time; break;
+                       case NUM_TEAM_3: pps_yellow += points/wait_time; break;
+                       case NUM_TEAM_4: pps_pink += points/wait_time; break;
+               }
+               total_pps += points/wait_time;
+       });
+
+       WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
+       WaypointSprite_Ping(this.sprite);
+
+       this.captime = time;
+
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
+}
+
+void AnimateDomPoint(entity this)
+{
+       if(this.pain_finished > time)
+               return;
+       this.pain_finished = time + this.t_width;
+       if(this.nextthink > this.pain_finished)
+               this.nextthink = this.pain_finished;
+
+       this.frame = this.frame + 1;
+       if(this.frame > this.t_length)
+               this.frame = 0;
+}
+
+void dompointthink(entity this)
+{
+       float fragamt;
+
+       this.nextthink = time + 0.1;
+
+       //this.frame = this.frame + 1;
+       //if(this.frame > 119)
+       //      this.frame = 0;
+       AnimateDomPoint(this);
+
+       // give points
+
+       if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
+               return;
+
+       if(autocvar_g_domination_point_rate)
+               this.delay = time + autocvar_g_domination_point_rate;
+       else
+               this.delay = time + this.wait;
+
+       // give credit to the team
+       // NOTE: this defaults to 0
+       if (!domination_roundbased)
+       if (this.goalentity.netname != "")
+       {
+               if(autocvar_g_domination_point_amt)
+                       fragamt = autocvar_g_domination_point_amt;
+               else
+                       fragamt = this.frags;
+               TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
+               TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
+
+               // give credit to the individual player, if he is still there
+               if (this.enemy.playerid == this.enemy_playerid)
+               {
+                       GameRules_scoring_add(this.enemy, SCORE, fragamt);
+                       GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
+               }
+               else
+                       this.enemy = NULL;
+       }
+}
+
+void dompointtouch(entity this, entity toucher)
+{
+       if(!IS_PLAYER(toucher))
+               return;
+       if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
+               return;
+
+       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+               return;
+
+       if(time < this.captime + 0.3)
+               return;
+
+       // only valid teams can claim it
+       entity head = find(NULL, classname, "dom_team");
+       while (head && head.team != toucher.team)
+               head = find(head, classname, "dom_team");
+       if (!head || head.netname == "" || head == this.goalentity)
+               return;
+
+       // delay capture
+
+       this.team = this.goalentity.team; // this stores the PREVIOUS team!
+
+       this.cnt = toucher.team;
+       this.owner = head; // team to switch to after the delay
+       this.dmg_inflictor = toucher;
+
+       // this.state = 1;
+       // this.delay = time + cvar("g_domination_point_capturetime");
+       //this.nextthink = time + cvar("g_domination_point_capturetime");
+       //this.think = dompoint_captured;
+
+       // go to neutral team in the mean time
+       head = find(NULL, classname, "dom_team");
+       while (head && head.netname != "")
+               head = find(head, classname, "dom_team");
+       if(head == NULL)
+               return;
+
+       WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
+       WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
+       WaypointSprite_Ping(this.sprite);
+
+       this.goalentity = head;
+       this.model = head.mdl;
+       this.modelindex = head.dmg;
+       this.skin = head.skin;
+
+       this.enemy = toucher; // individual player scoring
+       this.enemy_playerid = toucher.playerid;
+       dompoint_captured(this);
+}
+
+void dom_controlpoint_setup(entity this)
+{
+       entity head;
+       // find the spawnfunc_dom_team representing unclaimed points
+       head = find(NULL, classname, "dom_team");
+       while(head && head.netname != "")
+               head = find(head, classname, "dom_team");
+       if (!head)
+               objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
+
+       // copy important properties from spawnfunc_dom_team entity
+       this.goalentity = head;
+       _setmodel(this, head.mdl); // precision already set
+       this.skin = head.skin;
+
+       this.cnt = -1;
+
+       if(this.message == "")
+               this.message = " has captured a control point";
+
+       if(this.frags <= 0)
+               this.frags = 1;
+       if(this.wait <= 0)
+               this.wait = 5;
+
+       float points, waittime;
+       if (autocvar_g_domination_point_amt)
+               points = autocvar_g_domination_point_amt;
+       else
+               points = this.frags;
+       if (autocvar_g_domination_point_rate)
+               waittime = autocvar_g_domination_point_rate;
+       else
+               waittime = this.wait;
+
+       total_pps += points/waittime;
+
+       if(!this.t_width)
+               this.t_width = 0.02; // frame animation rate
+       if(!this.t_length)
+               this.t_length = 239; // maximum frame
+
+       setthink(this, dompointthink);
+       this.nextthink = time;
+       settouch(this, dompointtouch);
+       this.solid = SOLID_TRIGGER;
+       if(!this.flags & FL_ITEM)
+               IL_PUSH(g_items, this);
+       this.flags = FL_ITEM;
+       setsize(this, '-32 -32 -32', '32 32 32');
+       setorigin(this, this.origin + '0 0 20');
+       droptofloor(this);
+
+       waypoint_spawnforitem(this);
+       WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
+}
+
+int total_control_points;
+void Domination_count_controlpoints()
+{
+       total_control_points = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+       }
+       IL_EACH(g_dompoints, true,
+       {
+               ++total_control_points;
+               if (!Entity_HasValidTeam(it.goalentity))
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(it.goalentity);
+               int num_control_points = Team_GetNumberOfControlPoints(team_);
+               ++num_control_points;
+               Team_SetNumberOfControlPoints(team_, num_control_points);
+       });
+}
+
+int Domination_GetWinnerTeam()
+{
+       int winner_team = 0;
+       if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
+               total_control_points)
+       {
+               winner_team = NUM_TEAM_1;
+       }
+       for (int i = 2; i <= NUM_TEAMS; ++i)
+       {
+               if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
+                       total_control_points)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
+       }
+       if (winner_team)
+       {
+               return winner_team;
+       }
+       return -1; // no control points left?
+}
+
+bool Domination_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+               return true;
+       }
+
+       Domination_count_controlpoints();
+
+       float winner_team = Domination_GetWinnerTeam();
+
+       if(winner_team == -1)
+               return false;
+
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+
+       return true;
+}
+
+bool Domination_CheckPlayers()
+{
+       return true;
+}
+
+void Domination_RoundStart()
+{
+       FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
+}
+
+//go to best items, or control points you don't own
+void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
+{
+       IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
+       {
+               if(it.cnt > -1) // this is just being fought
+                       navigation_routerating(this, it, ratingscale, 5000);
+               else if(it.goalentity.cnt == 0) // unclaimed
+                       navigation_routerating(this, it, ratingscale, 5000);
+               else if(it.goalentity.team != this.team) // other team's point
+                       navigation_routerating(this, it, ratingscale, 5000);
+       });
+}
+
+void havocbot_role_dom(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
+               havocbot_goalrating_items(this, 20000, this.origin, 8000);
+               //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
+{
+       // fallback?
+       M_ARGV(0, float) = domination_teams;
+       string ret_string = "dom_team";
+
+       entity head = find(NULL, classname, ret_string);
+       while(head)
+       {
+               if(head.netname != "")
+               {
+                       if (Team_IsValidTeam(head.team))
+                       {
+                               M_ARGV(0, float) |= Team_TeamToBit(head.team);
+                       }
+               }
+
+               head = find(head, classname, ret_string);
+       }
+
+       M_ARGV(1, string) = string_null;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, reset_map_players)
+{
+       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               PutClientInServer(it);
+               if(domination_roundbased)
+                       it.player_blocked = 1;
+               if(IS_REAL_CLIENT(it))
+                       set_dom_state(it);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(domination_roundbased)
+       if(!round_handler_IsRoundStarted())
+               player.player_blocked = 1;
+       else
+               player.player_blocked = 0;
+}
+
+MUTATOR_HOOKFUNCTION(dom, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       set_dom_state(player);
+}
+
+MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       bot.havocbot_role = havocbot_role_dom;
+       return true;
+}
+
+/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
+Control point for Domination gameplay.
+*/
+spawnfunc(dom_controlpoint)
+{
+       if(!g_domination)
+       {
+               delete(this);
+               return;
+       }
+       setthink(this, dom_controlpoint_setup);
+       this.nextthink = time + 0.1;
+       this.reset = dom_controlpoint_setup;
+
+       if(!this.scale)
+               this.scale = 0.6;
+
+       this.effects = this.effects | EF_LOWPRECISION;
+       if (autocvar_g_domination_point_fullbright)
+               this.effects |= EF_FULLBRIGHT;
+
+       IL_PUSH(g_dompoints, this);
+}
+
+/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
+Team declaration for Domination gameplay, this allows you to decide what team
+names and control point models are used in your map.
+
+Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
+can have netname set!  The nameless team owns all control points at start.
+
+Keys:
+"netname"
+ Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
+"cnt"
+ Scoreboard color of the team (for example 4 is red and 13 is blue)
+"model"
+ Model to use for control points owned by this team (for example
+ "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
+ keycard)
+"skin"
+ Skin of the model to use (for team skins on a single model)
+"noise"
+ Sound to play when this team captures a point.
+ (this is a localized sound, like a small alarm or other effect)
+"noise1"
+ Narrator speech to play when this team captures a point.
+ (this is a global sound, like "Red team has captured a control point")
+*/
+
+spawnfunc(dom_team)
+{
+       if(!g_domination || autocvar_g_domination_teams_override >= 2)
+       {
+               delete(this);
+               return;
+       }
+       precache_model(this.model);
+       if (this.noise != "")
+               precache_sound(this.noise);
+       if (this.noise1 != "")
+               precache_sound(this.noise1);
+       this.classname = "dom_team";
+       _setmodel(this, this.model); // precision not needed
+       this.mdl = this.model;
+       this.dmg = this.modelindex;
+       this.model = "";
+       this.modelindex = 0;
+       // this would have to be changed if used in quakeworld
+       if(this.cnt)
+               this.team = this.cnt + 1; // WHY are these different anyway?
+}
+
+// scoreboard setup
+void ScoreRules_dom(int teams)
+{
+       if(domination_roundbased)
+       {
+           GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+            field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_DOM_TAKES, "takes", 0);
+           });
+       }
+       else
+       {
+               float sp_domticks, sp_score;
+               sp_score = sp_domticks = 0;
+               if(autocvar_g_domination_disable_frags)
+                       sp_domticks = SFL_SORT_PRIO_PRIMARY;
+               else
+                       sp_score = SFL_SORT_PRIO_PRIMARY;
+               GameRules_scoring(teams, sp_score, sp_score, {
+            field_team(ST_DOM_TICKS, "ticks", sp_domticks);
+            field(SP_DOM_TICKS, "ticks", sp_domticks);
+            field(SP_DOM_TAKES, "takes", 0);
+               });
+       }
+}
+
+// code from here on is just to support maps that don't have control point and team entities
+void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
+{
+       TC(Sound, capsound);
+       entity e = new_pure(dom_team);
+       e.netname = strzone(teamname);
+       e.cnt = teamcolor;
+       e.model = pointmodel;
+       e.skin = pointskin;
+       e.noise = strzone(Sound_fixpath(capsound));
+       e.noise1 = strzone(capnarration);
+       e.message = strzone(capmessage);
+
+       // this code is identical to spawnfunc_dom_team
+       _setmodel(e, e.model); // precision not needed
+       e.mdl = e.model;
+       e.dmg = e.modelindex;
+       e.model = "";
+       e.modelindex = 0;
+       // this would have to be changed if used in quakeworld
+       e.team = e.cnt + 1;
+
+       //eprint(e);
+}
+
+void dom_spawnpoint(vector org)
+{
+       entity e = spawn();
+       e.classname = "dom_controlpoint";
+       setthink(e, spawnfunc_dom_controlpoint);
+       e.nextthink = time;
+       setorigin(e, org);
+       spawnfunc_dom_controlpoint(e);
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams(int teams)
+{
+       TC(int, teams);
+       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
+       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point");
+       if(teams >= 3)
+               dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point");
+       if(teams >= 4)
+               dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point");
+       dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
+}
+
+void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+       // if no teams are found, spawn defaults
+       if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
+       {
+               LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
+               domination_teams = autocvar_g_domination_teams_override;
+               if (domination_teams < 2)
+                       domination_teams = autocvar_g_domination_default_teams;
+               domination_teams = bound(2, domination_teams, 4);
+               dom_spawnteams(domination_teams);
+       }
+
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       int teams = TeamBalance_GetAllowedTeams(balance);
+       TeamBalance_Destroy(balance);
+       domination_teams = teams;
+
+       domination_roundbased = autocvar_g_domination_roundbased;
+
+       ScoreRules_dom(domination_teams);
+
+       if(domination_roundbased)
+       {
+               round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
+               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+       }
+}
+
+void dom_Initialize()
+{
+       g_domination = true;
+       InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qh b/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qh
new file mode 100644 (file)
index 0000000..67e0008
--- /dev/null
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
+bool autocvar_g_domination_roundbased;
+int autocvar_g_domination_roundbased_point_limit;
+int autocvar_g_domination_point_leadlimit;
+
+void dom_Initialize();
+
+REGISTER_MUTATOR(dom, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               int fraglimit_override = autocvar_g_domination_point_limit;
+               if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
+                       fraglimit_override = autocvar_g_domination_roundbased_point_limit;
+
+               GameRules_teams(true);
+        GameRules_limit_score(fraglimit_override);
+        GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
+
+               dom_Initialize();
+       }
+       return 0;
+}
+
+// score rule declarations
+const float ST_DOM_TICKS = 1;
+const float ST_DOM_CAPS = 1;
+
+// pps: points per second
+float total_pps;
+float pps_red;
+float pps_blue;
+float pps_yellow;
+float pps_pink;
+
+// capture declarations
+.float enemy_playerid;
+.entity sprite;
+.float captime;
+
+// misc globals
+float domination_roundbased;
+float domination_teams;
+
+void AnimateDomPoint(entity this);
+
+IntrusiveList g_dompoints;
+STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
diff --git a/qcsrc/common/gamemodes/gamemode/duel/_mod.inc b/qcsrc/common/gamemodes/gamemode/duel/_mod.inc
new file mode 100644 (file)
index 0000000..5925816
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/duel/sv_duel.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/duel/_mod.qh b/qcsrc/common/gamemodes/gamemode/duel/_mod.qh
new file mode 100644 (file)
index 0000000..00e553c
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/duel/sv_duel.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/duel/sv_duel.qc b/qcsrc/common/gamemodes/gamemode/duel/sv_duel.qc
new file mode 100644 (file)
index 0000000..fc662e2
--- /dev/null
@@ -0,0 +1,22 @@
+#include "sv_duel.qh"
+
+MUTATOR_HOOKFUNCTION(duel, GetPlayerLimit)
+{
+       M_ARGV(0, int) = 2; // duel is always 1v1!
+}
+
+MUTATOR_HOOKFUNCTION(duel, Scores_CountFragsRemaining)
+{
+       // announce remaining frags?
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(duel, FilterItemDefinition)
+{
+       entity definition = M_ARGV(0, entity);
+
+       if(definition.instanceOfPowerup)
+       {
+               return !autocvar_g_duel_with_powerups;
+       }
+}
diff --git a/qcsrc/common/gamemodes/gamemode/duel/sv_duel.qh b/qcsrc/common/gamemodes/gamemode/duel/sv_duel.qh
new file mode 100644 (file)
index 0000000..d255c9b
--- /dev/null
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+REGISTER_MUTATOR(duel, false)
+{
+    MUTATOR_STATIC();
+       return 0;
+}
+
+bool autocvar_g_duel_with_powerups;
index aff5bf9d7b966ddd96981f7b147481b8ceee3713..4d628003561edf58724cf9a7eec923eb5a4d40e5 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/freezetag/freezetag.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/freezetag/sv_freezetag.qc>
+#endif
index 1bc21821a76fff3191633efbee6f893a968a38a1..785d7b81369428d0eda09b3d23362f542e63cb40 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/freezetag/freezetag.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/freezetag/sv_freezetag.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc
deleted file mode 100644 (file)
index 7f126ce..0000000
+++ /dev/null
@@ -1,593 +0,0 @@
-#include "freezetag.qh"
-
-// TODO: sv_freezetag
-#ifdef SVQC
-#include <server/resources.qh>
-
-float autocvar_g_freezetag_frozen_maxtime;
-float autocvar_g_freezetag_revive_clearspeed;
-float autocvar_g_freezetag_round_timelimit;
-//int autocvar_g_freezetag_teams;
-int autocvar_g_freezetag_teams_override;
-float autocvar_g_freezetag_warmup;
-
-void freezetag_count_alive_players()
-{
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               switch(it.team)
-               {
-                       case NUM_TEAM_1: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++redalive; break;
-                       case NUM_TEAM_2: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++bluealive; break;
-                       case NUM_TEAM_3: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++yellowalive; break;
-                       case NUM_TEAM_4: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++pinkalive; break;
-               }
-       });
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               STAT(REDALIVE, it) = redalive;
-               STAT(BLUEALIVE, it) = bluealive;
-               STAT(YELLOWALIVE, it) = yellowalive;
-               STAT(PINKALIVE, it) = pinkalive;
-       });
-
-       eliminatedPlayers.SendFlags |= 1;
-}
-#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams))
-
-float freezetag_CheckTeams()
-{
-       static float prev_missing_teams_mask;
-       if(FREEZETAG_ALIVE_TEAMS_OK())
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 1;
-       }
-       if(total_players == 0)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 0;
-       }
-       int missing_teams_mask = 0;
-       if(freezetag_teams & BIT(0))
-               missing_teams_mask += (!redalive) * 1;
-       if(freezetag_teams & BIT(1))
-               missing_teams_mask += (!bluealive) * 2;
-       if(freezetag_teams & BIT(2))
-               missing_teams_mask += (!yellowalive) * 4;
-       if(freezetag_teams & BIT(3))
-               missing_teams_mask += (!pinkalive) * 8;
-       if(prev_missing_teams_mask != missing_teams_mask)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-               prev_missing_teams_mask = missing_teams_mask;
-       }
-       return 0;
-}
-
-float freezetag_getWinnerTeam()
-{
-       float winner_team = 0;
-       if(redalive >= 1)
-               winner_team = NUM_TEAM_1;
-       if(bluealive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no player left
-}
-
-void nades_Clear(entity);
-void nades_GiveBonus(entity player, float score);
-
-float freezetag_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       it.freezetag_frozen_timeout = 0;
-                       nades_Clear(it);
-               });
-               game_stopped = true;
-               round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-               return 1;
-       }
-
-       if(FREEZETAG_ALIVE_TEAMS() > 1)
-               return 0;
-
-       int winner_team = freezetag_getWinnerTeam();
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               it.freezetag_frozen_timeout = 0;
-               nades_Clear(it);
-       });
-
-       game_stopped = true;
-       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-       return 1;
-}
-
-entity freezetag_LastPlayerForTeam(entity this)
-{
-       entity last_pl = NULL;
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
-               if (!STAT(FROZEN, it) && GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
-               {
-                       if (!last_pl)
-                               last_pl = it;
-                       else
-                               return NULL;
-               }
-       });
-       return last_pl;
-}
-
-void freezetag_LastPlayerForTeam_Notify(entity this)
-{
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               entity pl = freezetag_LastPlayerForTeam(this);
-               if(pl)
-                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
-       }
-}
-
-void freezetag_Add_Score(entity targ, entity attacker)
-{
-       if(attacker == targ)
-       {
-               // you froze your own dumb targ
-               // counted as "suicide" already
-               GameRules_scoring_add(targ, SCORE, -1);
-       }
-       else if(IS_PLAYER(attacker))
-       {
-               // got frozen by an enemy
-               // counted as "kill" and "death" already
-               GameRules_scoring_add(targ, SCORE, -1);
-               GameRules_scoring_add(attacker, SCORE, +1);
-       }
-       // else nothing - got frozen by the game type rules themselves
-}
-
-// to be called when the player is frozen by freezetag (on death, spectator join etc), gives the score
-void freezetag_Freeze(entity targ, entity attacker)
-{
-       if(STAT(FROZEN, targ))
-               return;
-
-       if(autocvar_g_freezetag_frozen_maxtime > 0)
-               targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
-       Freeze(targ, 0, 1, true);
-
-       freezetag_count_alive_players();
-
-       freezetag_Add_Score(targ, attacker);
-}
-
-float freezetag_isEliminated(entity e)
-{
-       if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
-               return true;
-       return false;
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void(entity this) havocbot_role_ft_freeing;
-void(entity this) havocbot_role_ft_offense;
-
-void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
-{
-       float t;
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
-               if (STAT(FROZEN, it) == 1)
-               {
-                       if(vdist(it.origin - org, >, sradius))
-                               continue;
-                       navigation_routerating(this, it, ratingscale, 2000);
-               }
-               else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
-               {
-                       // If teamate is not frozen still seek them out as fight better
-                       // in a group.
-                       t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
-                       navigation_routerating(this, it, t * ratingscale, 2000);
-               }
-       });
-}
-
-void havocbot_role_ft_offense(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-
-       // Count how many players on team are unfrozen.
-       int unfrozen = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
-
-       // If only one left on team or if role has timed out then start trying to free players.
-       if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
-       {
-               LOG_TRACE("changing role to freeing");
-               this.havocbot_role = havocbot_role_ft_freeing;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ft_freeing(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to offense");
-               this.havocbot_role = havocbot_role_ft_offense;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 8000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
-               havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-void ft_RemovePlayer(entity this)
-{
-       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to update correctly alive stats
-       if(!STAT(FROZEN, this))
-               freezetag_LastPlayerForTeam_Notify(this);
-       Unfreeze(this);
-       freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       ft_RemovePlayer(player);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       ft_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_deathtype = M_ARGV(3, float);
-
-       if(round_handler_IsActive())
-       if(round_handler_CountdownRunning())
-       {
-               if(STAT(FROZEN, frag_target))
-                       Unfreeze(frag_target);
-               freezetag_count_alive_players();
-               return true; // let the player die so that he can respawn whenever he wants
-       }
-
-       // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
-       // you succeed changing team through the menu: you both really die (gibbing) and get frozen
-       if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
-               || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
-       {
-               // let the player die, he will be automatically frozen when he respawns
-               if(STAT(FROZEN, frag_target) != 1)
-               {
-                       freezetag_Add_Score(frag_target, frag_attacker);
-                       freezetag_count_alive_players();
-                       freezetag_LastPlayerForTeam_Notify(frag_target);
-               }
-               else
-                       Unfreeze(frag_target); // remove ice
-               SetResourceAmountExplicit(frag_target, RESOURCE_HEALTH, 0); // Unfreeze resets health
-               frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
-               return true;
-       }
-
-       if(STAT(FROZEN, frag_target))
-               return true;
-
-       freezetag_Freeze(frag_target, frag_attacker);
-       freezetag_LastPlayerForTeam_Notify(frag_target);
-
-       if(frag_attacker == frag_target || frag_attacker == NULL)
-       {
-               if(IS_PLAYER(frag_target))
-                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
-       }
-       else
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
-               return true; // do nothing, round is starting right now
-
-       if(player.freezetag_frozen_timeout == -2) // player was dead
-       {
-               freezetag_Freeze(player, NULL);
-               return true;
-       }
-
-       freezetag_count_alive_players();
-
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
-               freezetag_Freeze(player, NULL);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, reset_map_players)
-{
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               CS(it).killcount = 0;
-               it.freezetag_frozen_timeout = -1;
-               PutClientInServer(it);
-               it.freezetag_frozen_timeout = 0;
-       });
-       freezetag_count_alive_players();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, Unfreeze)
-{
-       entity targ = M_ARGV(0, entity);
-       targ.freezetag_frozen_time = 0;
-       targ.freezetag_frozen_timeout = 0;
-
-       freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
-{
-       if(game_stopped)
-               return true;
-
-       if(round_handler_IsActive())
-       if(!round_handler_IsRoundStarted())
-               return true;
-
-       int n;
-       entity o = NULL;
-       entity player = M_ARGV(0, entity);
-       //if(STAT(FROZEN, player))
-       //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
-               //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
-
-       if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
-               n = -1;
-       else
-       {
-               vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
-               n = 0;
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
-                       if(STAT(FROZEN, it) == 0)
-                       if(!IS_DEAD(it))
-                       if(SAME_TEAM(it, player))
-                       if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
-                       {
-                               if(!o)
-                                       o = it;
-                               if(STAT(FROZEN, player) == 1)
-                                       it.reviving = true;
-                               ++n;
-                       }
-               });
-
-       }
-
-       if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
-       {
-               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
-               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
-
-               if(STAT(REVIVE_PROGRESS, player) >= 1)
-               {
-                       Unfreeze(player);
-                       freezetag_count_alive_players();
-
-                       if(n == -1)
-                       {
-                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
-                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
-                               return true;
-                       }
-
-                       // EVERY team mate nearby gets a point (even if multiple!)
-                       FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
-                               GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
-                               GameRules_scoring_add(it, SCORE, +1);
-                               nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
-                       });
-
-                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
-                       Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
-               }
-
-               FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
-                       STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
-                       it.reviving = false;
-               });
-       }
-       else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
-       {
-               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
-               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
-       }
-       else if(!n && !STAT(FROZEN, player))
-       {
-               STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetStartItems)
-{
-       start_items &= ~IT_UNLIMITED_AMMO;
-       //start_health       = warmup_start_health       = cvar("g_lms_start_health");
-       //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
-       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
-       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
-       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
-       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
-       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
-       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
-}
-
-MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       if (!IS_DEAD(bot))
-       {
-               if (random() < 0.5)
-                       bot.havocbot_role = havocbot_role_ft_freeing;
-               else
-                       bot.havocbot_role = havocbot_role_ft_offense;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = freezetag_teams;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
-{
-       // most weapons arena
-       if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
-               M_ARGV(0, string) = "most";
-}
-
-MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
-{
-       entity frag_attacker = M_ARGV(0, entity);
-       entity frag_target = M_ARGV(1, entity);
-       //float frag_deathtype = M_ARGV(2, float);
-       int kill_count_to_attacker = M_ARGV(3, int);
-       int kill_count_to_target = M_ARGV(4, int);
-
-       if(STAT(FROZEN, frag_target))
-               return; // target was already frozen, so this is just pushing them off the cliff
-
-       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
-       Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
-               GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
-
-       return true;
-}
-
-void freezetag_Initialize()
-{
-       freezetag_teams = autocvar_g_freezetag_teams_override;
-       if(freezetag_teams < 2)
-               freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
-
-       freezetag_teams = BITS(bound(2, freezetag_teams, 4));
-       GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
-               field(SP_FREEZETAG_REVIVALS, "revivals", 0);
-       });
-
-       round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
-       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-
-       EliminatedPlayers_Init(freezetag_isEliminated);
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh
deleted file mode 100644 (file)
index ed38ae5..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-int autocvar_g_freezetag_point_limit;
-int autocvar_g_freezetag_point_leadlimit;
-bool autocvar_g_freezetag_team_spawns;
-void freezetag_Initialize();
-
-REGISTER_MUTATOR(ft, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
-        GameRules_limit_score(autocvar_g_freezetag_point_limit);
-        GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
-
-               freezetag_Initialize();
-       }
-       return 0;
-}
-
-.float freezetag_frozen_time;
-.float freezetag_frozen_timeout;
-const float ICE_MAX_ALPHA = 1;
-const float ICE_MIN_ALPHA = 0.1;
-float freezetag_teams;
-
-.float reviving; // temp var
-
-float autocvar_g_freezetag_revive_extra_size;
-float autocvar_g_freezetag_revive_speed;
-bool autocvar_g_freezetag_revive_nade;
-float autocvar_g_freezetag_revive_nade_health;
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc
new file mode 100644 (file)
index 0000000..0b87c03
--- /dev/null
@@ -0,0 +1,625 @@
+#include "sv_freezetag.qh"
+
+#include <server/resources.qh>
+
+float autocvar_g_freezetag_frozen_maxtime;
+float autocvar_g_freezetag_revive_clearspeed;
+float autocvar_g_freezetag_round_timelimit;
+//int autocvar_g_freezetag_teams;
+int autocvar_g_freezetag_teams_override;
+float autocvar_g_freezetag_warmup;
+
+void freezetag_count_alive_players()
+{
+       total_players = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+       }
+       FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+       {
+               ++total_players;
+               if (GetResourceAmount(it, RESOURCE_HEALTH) < 1 || STAT(FROZEN, it) == FROZEN_NORMAL)
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(it);
+               int num_alive = Team_GetNumberOfAlivePlayers(team_);
+               ++num_alive;
+               Team_SetNumberOfAlivePlayers(team_, num_alive);
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+                       1));
+               STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(2));
+               STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(3));
+               STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(4));
+       });
+
+       eliminatedPlayers.SendFlags |= 1;
+}
+
+#define FREEZETAG_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(freezetag_teams))
+
+bool freezetag_CheckTeams()
+{
+       static float prev_missing_teams_mask;
+       if(FREEZETAG_ALIVE_TEAMS_OK())
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return true;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return false;
+       }
+       int missing_teams_mask = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if ((freezetag_teams & Team_IndexToBit(i)) &&
+                       (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+               {
+                       missing_teams_mask |= Team_IndexToBit(i);
+               }
+       }
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+               prev_missing_teams_mask = missing_teams_mask;
+       }
+       return false;
+}
+
+int freezetag_getWinnerTeam()
+{
+       int winner_team = 0;
+       if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
+       {
+               winner_team = NUM_TEAM_1;
+       }
+       for (int i = 2; i <= NUM_TEAMS; ++i)
+       {
+               if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
+       }
+       if (winner_team)
+       {
+               return winner_team;
+       }
+       return -1; // no player left
+}
+
+void nades_Clear(entity);
+void nades_GiveBonus(entity player, float score);
+
+bool freezetag_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       it.freezetag_frozen_timeout = 0;
+                       nades_Clear(it);
+               });
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+               return true;
+       }
+
+       if (Team_GetNumberOfAliveTeams() > 1)
+       {
+               return false;
+       }
+
+       int winner_team = freezetag_getWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               it.freezetag_frozen_timeout = 0;
+               nades_Clear(it);
+       });
+
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+       return true;
+}
+
+entity freezetag_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
+               if (STAT(FROZEN, it) != FROZEN_NORMAL && GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
+               {
+                       if (!last_pl)
+                               last_pl = it;
+                       else
+                               return NULL;
+               }
+       });
+       return last_pl;
+}
+
+void freezetag_LastPlayerForTeam_Notify(entity this)
+{
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               entity pl = freezetag_LastPlayerForTeam(this);
+               if(pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+void freezetag_Add_Score(entity targ, entity attacker)
+{
+       if(attacker == targ)
+       {
+               // you froze your own dumb targ
+               // counted as "suicide" already
+               GameRules_scoring_add(targ, SCORE, -1);
+       }
+       else if(IS_PLAYER(attacker))
+       {
+               // got frozen by an enemy
+               // counted as "kill" and "death" already
+               GameRules_scoring_add(targ, SCORE, -1);
+               GameRules_scoring_add(attacker, SCORE, +1);
+       }
+       // else nothing - got frozen by the game type rules themselves
+}
+
+// to be called when the player is frozen by freezetag (on death, spectator join etc), gives the score
+void freezetag_Freeze(entity targ, entity attacker)
+{
+       if(STAT(FROZEN, targ))
+               return;
+
+       if(autocvar_g_freezetag_frozen_maxtime > 0)
+               targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
+
+       Freeze(targ, 0, FROZEN_NORMAL, true);
+
+       freezetag_count_alive_players();
+
+       freezetag_Add_Score(targ, attacker);
+}
+
+bool freezetag_isEliminated(entity e)
+{
+       if(IS_PLAYER(e) && (STAT(FROZEN, e) == FROZEN_NORMAL || IS_DEAD(e)))
+               return true;
+       return false;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void(entity this) havocbot_role_ft_freeing;
+void(entity this) havocbot_role_ft_offense;
+
+void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
+{
+       entity best_pl = NULL;
+       float best_dist2 = FLOAT_MAX;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
+               if (STAT(FROZEN, it) == FROZEN_NORMAL)
+               {
+                       if(vdist(it.origin - org, >, sradius))
+                               continue;
+                       navigation_routerating(this, it, ratingscale, 2000);
+               }
+               else if (best_dist2
+                       && GetResourceAmount(it, RESOURCE_HEALTH) < GetResourceAmount(this, RESOURCE_HEALTH) + 30
+                       && vlen2(it.origin - org) < best_dist2)
+               {
+                       // If teamate is not frozen still seek them out as fight better
+                       // in a group.
+                       best_dist2 = vlen2(it.origin - org);
+                       if (best_dist2 < 700 ** 2)
+                       {
+                               best_pl = NULL;
+                               best_dist2 = 0; // already close to a teammate
+                       }
+                       else
+                               best_pl = it;
+               }
+       });
+       if (best_pl)
+               navigation_routerating(this, best_pl, ratingscale / 2, 2000);
+}
+
+void havocbot_role_ft_offense(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+
+       // Count how many players on team are unfrozen.
+       int unfrozen = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && STAT(FROZEN, it) != FROZEN_NORMAL, {
+               unfrozen++;
+       });
+
+       // If only one left on team or if role has timed out then start trying to free players.
+       if ((!unfrozen && STAT(FROZEN, this) != FROZEN_NORMAL) || time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freeing");
+               this.havocbot_role = havocbot_role_ft_freeing;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 12000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+               havocbot_goalrating_ft_freeplayers(this, 9000, this.origin, 10000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ft_freeing(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to offense");
+               this.havocbot_role = havocbot_role_ft_offense;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
+               havocbot_goalrating_ft_freeplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+void ft_RemovePlayer(entity this)
+{
+       if (STAT(FROZEN, this) != FROZEN_NORMAL)
+               freezetag_LastPlayerForTeam_Notify(this);
+       Unfreeze(this, false);
+
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to correctly count alive players
+       freezetag_count_alive_players();
+}
+
+MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       ft_RemovePlayer(player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       ft_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+
+       if(round_handler_IsActive())
+       if(round_handler_CountdownRunning())
+       {
+               if (STAT(FROZEN, frag_target) == FROZEN_NORMAL)
+                       Unfreeze(frag_target, true);
+               freezetag_count_alive_players();
+               return true; // let the player die so that he can respawn whenever he wants
+       }
+
+       // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
+       // you succeed changing team through the menu: you both really die (gibbing) and get frozen
+       if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
+               || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
+       {
+               // let the player die, he will be automatically frozen when he respawns
+               if (STAT(FROZEN, frag_target) != FROZEN_NORMAL)
+               {
+                       freezetag_Add_Score(frag_target, frag_attacker);
+                       freezetag_count_alive_players();
+                       freezetag_LastPlayerForTeam_Notify(frag_target);
+               }
+               else
+                       Unfreeze(frag_target, false); // remove ice
+               frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
+               return true;
+       }
+
+       if (STAT(FROZEN, frag_target) == FROZEN_NORMAL)
+               return true;
+
+       freezetag_Freeze(frag_target, frag_attacker);
+       freezetag_LastPlayerForTeam_Notify(frag_target);
+
+       if(frag_attacker == frag_target || frag_attacker == NULL)
+       {
+               if(IS_PLAYER(frag_target))
+                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
+       }
+       else
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
+               return true; // do nothing, round is starting right now
+
+       if(player.freezetag_frozen_timeout == -2) // player was dead
+       {
+               freezetag_Freeze(player, NULL);
+               return true;
+       }
+
+       freezetag_count_alive_players();
+
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
+               freezetag_Freeze(player, NULL);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, reset_map_players)
+{
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               CS(it).killcount = 0;
+               it.freezetag_frozen_timeout = -1;
+               PutClientInServer(it);
+               it.freezetag_frozen_timeout = 0;
+       });
+       freezetag_count_alive_players();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, Unfreeze)
+{
+       entity targ = M_ARGV(0, entity);
+       targ.freezetag_frozen_time = 0;
+       targ.freezetag_frozen_timeout = 0;
+}
+
+#ifdef IS_REVIVING
+       #undef IS_REVIVING
+#endif
+
+// returns true if player is reviving it
+#define IS_REVIVING(player, it, revive_extra_size) \
+       (it != player && !STAT(FROZEN, it) && !IS_DEAD(it) && SAME_TEAM(it, player) \
+       && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+
+MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
+{
+       if(game_stopped)
+               return true;
+
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+               return true;
+
+       int n;
+       entity player = M_ARGV(0, entity);
+       //if (STAT(FROZEN, player) == FROZEN_NORMAL)
+       //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
+               //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
+
+       IntrusiveList reviving_players = NULL;
+
+       if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
+               n = -1;
+       else
+       {
+               n = 0;
+               vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
+               FOREACH_CLIENT(IS_PLAYER(it) && IS_REVIVING(player, it, revive_extra_size), {
+                       if (!reviving_players)
+                               reviving_players = IL_NEW();
+                       IL_PUSH(reviving_players, it);
+                       ++n;
+               });
+       }
+
+       if (!n) // no teammate nearby
+       {
+               if (STAT(FROZEN, player) == FROZEN_NORMAL)
+               {
+                       STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
+                       SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+               }
+               else if (!STAT(FROZEN, player))
+                       STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
+       }
+       else if (STAT(FROZEN, player) == FROZEN_NORMAL) // OK, there is at least one teammate reviving us
+       {
+               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
+               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+
+               if(STAT(REVIVE_PROGRESS, player) >= 1)
+               {
+                       Unfreeze(player, false);
+                       freezetag_count_alive_players();
+
+                       if(n == -1)
+                       {
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
+                               return true;
+                       }
+
+                       // EVERY team mate nearby gets a point (even if multiple!)
+                       IL_EACH(reviving_players, true, {
+                               GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
+                               GameRules_scoring_add(it, SCORE, +1);
+                               nades_GiveBonus(it, autocvar_g_nades_bonus_score_low);
+                       });
+
+                       entity first = IL_FIRST(reviving_players);
+                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, first.netname);
+                       Send_Notification(NOTIF_ONE, first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, first.netname);
+               }
+
+               IL_EACH(reviving_players, true, {
+                       STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
+               });
+       }
+
+       if (reviving_players)
+               IL_DELETE(reviving_players);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetStartItems)
+{
+       start_items &= ~IT_UNLIMITED_AMMO;
+       //start_health       = warmup_start_health       = cvar("g_lms_start_health");
+       //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
+       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
+       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
+       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
+       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
+       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
+}
+
+MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       if (!IS_DEAD(bot))
+       {
+               if (random() < 0.5)
+                       bot.havocbot_role = havocbot_role_ft_freeing;
+               else
+                       bot.havocbot_role = havocbot_role_ft_offense;
+       }
+
+       // if bots spawn all at once assign them a more appropriated role after a while
+       if (time < CS(bot).jointime + 1)
+               bot.havocbot_role_timeout = time + 10 + random() * 10;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = freezetag_teams;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
+{
+       // most weapons arena
+       if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
+               M_ARGV(0, string) = "most";
+}
+
+MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
+{
+       entity frag_attacker = M_ARGV(0, entity);
+       entity frag_target = M_ARGV(1, entity);
+       //float frag_deathtype = M_ARGV(2, float);
+       int kill_count_to_attacker = M_ARGV(3, int);
+       int kill_count_to_target = M_ARGV(4, int);
+
+       if(STAT(FROZEN, frag_target) == FROZEN_NORMAL)
+               return; // target was already frozen, so this is just pushing them off the cliff
+
+       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
+       Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
+               GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
+
+       return true;
+}
+
+void freezetag_Initialize()
+{
+       freezetag_teams = autocvar_g_freezetag_teams_override;
+       if(freezetag_teams < 2)
+               freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+       freezetag_teams = BITS(bound(2, freezetag_teams, 4));
+       GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+               field(SP_FREEZETAG_REVIVALS, "revivals", 0);
+       });
+
+       round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
+       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+
+       EliminatedPlayers_Init(freezetag_isEliminated);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh
new file mode 100644 (file)
index 0000000..d637ae4
--- /dev/null
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+int autocvar_g_freezetag_point_limit;
+int autocvar_g_freezetag_point_leadlimit;
+bool autocvar_g_freezetag_team_spawns;
+void freezetag_Initialize();
+
+REGISTER_MUTATOR(ft, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
+        GameRules_limit_score(autocvar_g_freezetag_point_limit);
+        GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
+
+               freezetag_Initialize();
+       }
+       return 0;
+}
+
+.float freezetag_frozen_time;
+.float freezetag_frozen_timeout;
+const float ICE_MAX_ALPHA = 1;
+const float ICE_MIN_ALPHA = 0.1;
+float freezetag_teams;
+
+float autocvar_g_freezetag_revive_extra_size;
+float autocvar_g_freezetag_revive_speed;
+bool autocvar_g_freezetag_revive_nade;
+float autocvar_g_freezetag_revive_nade_health;
index 905aa0611bdd739af535f18d0e420b078996cf9e..a197891fe8737bfac787195ce64c093b8c2dba55 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/invasion/invasion.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/invasion/sv_invasion.qc>
+#endif
index d8e8d223dae4f2805f6616912fe40a31cb4dc0cf..f90ea9b31e7c8bbf3d0649ab7574c937eb27e59e 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/invasion/invasion.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/invasion/sv_invasion.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc
deleted file mode 100644 (file)
index 3dff701..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-#include "invasion.qh"
-
-// TODO: sv_invasion
-#ifdef SVQC
-#include <common/monsters/sv_spawn.qh>
-#include <common/monsters/sv_spawner.qh>
-#include <common/monsters/sv_monsters.qh>
-
-#include <server/teamplay.qh>
-
-IntrusiveList g_invasion_roundends;
-IntrusiveList g_invasion_waves;
-IntrusiveList g_invasion_spawns;
-STATIC_INIT(g_invasion)
-{
-       g_invasion_roundends = IL_NEW();
-       g_invasion_waves = IL_NEW();
-       g_invasion_spawns = IL_NEW();
-}
-
-float autocvar_g_invasion_round_timelimit;
-float autocvar_g_invasion_spawnpoint_spawn_delay;
-float autocvar_g_invasion_warmup;
-int autocvar_g_invasion_monster_count;
-bool autocvar_g_invasion_zombies_only;
-float autocvar_g_invasion_spawn_delay;
-
-bool victent_present;
-.bool inv_endreached;
-
-bool inv_warning_shown; // spammy
-
-void target_invasion_roundend_use(entity this, entity actor, entity trigger)
-{
-       if(!IS_PLAYER(actor)) { return; }
-
-       actor.inv_endreached = true;
-
-       int plnum = 0;
-       int realplnum = 0;
-       // let's not count bots
-       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
-               ++realplnum;
-               if(it.inv_endreached)
-                       ++plnum;
-       });
-       if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
-               return;
-
-       this.winning = true;
-}
-
-spawnfunc(target_invasion_roundend)
-{
-       if(!g_invasion) { delete(this); return; }
-
-       victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
-
-       if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
-
-       this.use = target_invasion_roundend_use;
-
-       IL_PUSH(g_invasion_roundends, this);
-}
-
-spawnfunc(invasion_wave)
-{
-       if(!g_invasion) { delete(this); return; }
-
-       IL_PUSH(g_invasion_waves, this);
-}
-
-spawnfunc(invasion_spawnpoint)
-{
-       if(!g_invasion) { delete(this); return; }
-
-       this.classname = "invasion_spawnpoint";
-       IL_PUSH(g_invasion_spawns, this);
-}
-
-void ClearWinners();
-
-// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win.
-int WinningCondition_Invasion()
-{
-       WinningConditionHelper(NULL); // set worldstatus
-
-       int status = WINNING_NO;
-
-       if(autocvar_g_invasion_type == INV_TYPE_STAGE)
-       {
-               SetWinners(inv_endreached, true);
-
-               int found = 0;
-               IL_EACH(g_invasion_roundends, true,
-               {
-                       ++found;
-                       if(it.winning)
-                       {
-                               bprint("Invasion: round completed.\n");
-                               // winners already set (TODO: teamplay support)
-
-                               status = WINNING_YES;
-                               break;
-                       }
-               });
-
-               if(!found)
-                       status = WINNING_YES; // just end it? TODO: should warn mapper!
-       }
-       else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
-       {
-               ClearWinners();
-
-               int found = 0; // NOTE: this ends the round if no monsters are placed
-               IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
-               {
-                       ++found;
-               });
-
-               if(found <= 0)
-               {
-                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
-                       {
-                               it.winning = true;
-                       });
-                       status = WINNING_YES;
-               }
-       }
-
-       return status;
-}
-
-Monster invasion_PickMonster(int supermonster_count)
-{
-       RandomSelection_Init();
-
-       FOREACH(Monsters, it != MON_Null,
-       {
-               if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
-                       (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
-                       continue;
-               if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
-                       continue;
-        RandomSelection_AddEnt(it, 1, 1);
-       });
-
-       return RandomSelection_chosen_ent;
-}
-
-entity invasion_PickSpawn()
-{
-       RandomSelection_Init();
-
-       IL_EACH(g_invasion_spawns, true,
-       {
-               RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
-               it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
-       });
-
-       return RandomSelection_chosen_ent;
-}
-
-entity invasion_GetWaveEntity(int wavenum)
-{
-       IL_EACH(g_invasion_waves, it.cnt == wavenum,
-       {
-               return it; // found one
-       });
-
-       // if no specific one is found, find the last existing wave ent
-       entity best = NULL;
-       IL_EACH(g_invasion_waves, it.cnt <= wavenum,
-       {
-               if(!best || it.cnt > best.cnt)
-                       best = it;
-       });
-
-       return best;
-}
-
-void invasion_SpawnChosenMonster(Monster mon)
-{
-       entity monster;
-       entity spawn_point = invasion_PickSpawn();
-       entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
-
-       string tospawn = "";
-       if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
-       {
-               RandomSelection_Init();
-               FOREACH_WORD(wave_ent.spawnmob, true,
-               {
-                       RandomSelection_AddString(it, 1, 1);
-               });
-
-               tospawn = RandomSelection_chosen_string;
-       }
-
-       if(spawn_point == NULL)
-       {
-               if(!inv_warning_shown)
-               {
-                       inv_warning_shown = true;
-                       LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
-               }
-               entity e = spawn();
-               setsize(e, mon.m_mins, mon.m_maxs);
-
-               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
-                       monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
-               else
-               {
-                       delete(e);
-                       return;
-               }
-       }
-       else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
-               monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
-
-       if(!monster)
-               return;
-
-       monster.spawnshieldtime = time;
-
-       if(spawn_point)
-       {
-               if(spawn_point.target_range)
-                       monster.target_range = spawn_point.target_range;
-               monster.target2 = spawn_point.target2;
-       }
-
-       if(teamplay)
-       {
-               if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
-                       monster.team = spawn_point.team;
-               else
-               {
-                       RandomSelection_Init();
-                       if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
-                       if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
-                       if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
-                       if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
-
-                       monster.team = RandomSelection_chosen_float;
-               }
-
-               monster_setupcolors(monster);
-
-               if(monster.sprite)
-               {
-                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
-
-                       monster.sprite.team = 0;
-                       monster.sprite.SendFlags |= 1;
-               }
-       }
-
-       if(monster.monster_attack)
-               IL_REMOVE(g_monster_targets, monster);
-       monster.monster_attack = false; // it's the player's job to kill all the monsters
-
-       if(inv_roundcnt >= inv_maxrounds)
-               monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
-}
-
-void invasion_SpawnMonsters(int supermonster_count)
-{
-       Monster chosen_monster = invasion_PickMonster(supermonster_count);
-
-       invasion_SpawnChosenMonster(chosen_monster);
-}
-
-bool Invasion_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               IL_EACH(g_monsters, true,
-               {
-                       Monster_Remove(it);
-               });
-               IL_CLEAR(g_monsters);
-
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-               round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-               return 1;
-       }
-
-       float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
-
-       IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
-       {
-               if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
-                       ++supermonster_count;
-               ++total_alive_monsters;
-
-               if(teamplay)
-               switch(it.team)
-               {
-                       case NUM_TEAM_1: ++red_alive; break;
-                       case NUM_TEAM_2: ++blue_alive; break;
-                       case NUM_TEAM_3: ++yellow_alive; break;
-                       case NUM_TEAM_4: ++pink_alive; break;
-               }
-       });
-
-       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
-       {
-               if(time >= inv_lastcheck)
-               {
-                       invasion_SpawnMonsters(supermonster_count);
-                       inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
-               }
-
-               return 0;
-       }
-
-       if(inv_numspawned < 1)
-               return 0; // nothing has spawned yet
-
-       if(teamplay)
-       {
-               if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
-                       return 0;
-       }
-       else if(inv_numkilled < inv_maxspawned)
-               return 0;
-
-       entity winner = NULL;
-       float winning_score = 0, winner_team = 0;
-
-
-       if(teamplay)
-       {
-               if(red_alive > 0) { winner_team = NUM_TEAM_1; }
-               if(blue_alive > 0)
-               if(winner_team) { winner_team = 0; }
-               else { winner_team = NUM_TEAM_2; }
-               if(yellow_alive > 0)
-               if(winner_team) { winner_team = 0; }
-               else { winner_team = NUM_TEAM_3; }
-               if(pink_alive > 0)
-               if(winner_team) { winner_team = 0; }
-               else { winner_team = NUM_TEAM_4; }
-       }
-       else
-       {
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       float cs = GameRules_scoring_add(it, KILLS, 0);
-                       if(cs > winning_score)
-                       {
-                               winning_score = cs;
-                               winner = it;
-                       }
-               });
-       }
-
-       IL_EACH(g_monsters, true,
-       {
-               Monster_Remove(it);
-       });
-       IL_CLEAR(g_monsters);
-
-       if(teamplay)
-       {
-               if(winner_team)
-               {
-                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               }
-       }
-       else if(winner)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
-       }
-
-       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
-       return 1;
-}
-
-bool Invasion_CheckPlayers()
-{
-       return true;
-}
-
-void Invasion_RoundStart()
-{
-       int numplayers = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               it.player_blocked = false;
-               ++numplayers;
-       });
-
-       if(inv_roundcnt < inv_maxrounds)
-               inv_roundcnt += 1; // a limiter to stop crazy counts
-
-       inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
-
-       inv_maxcurrent = 0;
-       inv_numspawned = 0;
-       inv_numkilled = 0;
-
-       inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
-
-       if(teamplay)
-       {
-               DistributeEvenly_Init(inv_maxspawned, invasion_teams);
-               inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
-               inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
-               if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
-               if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterDies)
-{
-       entity frag_target = M_ARGV(0, entity);
-       entity frag_attacker = M_ARGV(1, entity);
-
-       if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
-       {
-               if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-               {
-                       inv_numkilled += 1;
-                       inv_maxcurrent -= 1;
-               }
-               if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
-
-               if(IS_PLAYER(frag_attacker))
-               if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
-                       GameRules_scoring_add(frag_attacker, KILLS, -1);
-               else
-               {
-                       GameRules_scoring_add(frag_attacker, KILLS, +1);
-                       if(teamplay)
-                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
-{
-       entity mon = M_ARGV(0, entity);
-       mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
-       if(autocvar_g_invasion_type == INV_TYPE_HUNT)
-               return false; // allowed
-
-       if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
-               return true;
-
-       if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
-       {
-               inv_numspawned += 1;
-               inv_maxcurrent += 1;
-       }
-
-       mon.monster_skill = inv_monsterskill;
-
-       if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
-}
-
-MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
-{
-       if(autocvar_g_invasion_type != INV_TYPE_ROUND)
-               return; // uses map spawned monsters
-
-       monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
-       monsters_killed = inv_numkilled;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
-{
-       // no regeneration in invasion, regardless of the game type
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.bot_attack)
-               IL_REMOVE(g_bot_targets, player);
-       player.bot_attack = false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(4, float);
-       vector frag_force = M_ARGV(6, vector);
-
-       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
-       {
-               frag_damage = 0;
-               frag_force = '0 0 0';
-
-               M_ARGV(4, float) = frag_damage;
-               M_ARGV(6, vector) = frag_force;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
-{
-       entity targ = M_ARGV(1, entity);
-
-       if(!IS_MONSTER(targ))
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SetStartItems)
-{
-       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-       {
-               start_health = 200;
-               start_armorvalue = 200;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
-{
-       entity frag_target = M_ARGV(1, entity);
-
-       if(IS_MONSTER(frag_target))
-               return MUT_ACCADD_INVALID;
-       return MUT_ACCADD_INDIFFERENT;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
-{
-       // monster spawning disabled during an invasion
-       M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
-{
-       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-               return false;
-
-       M_ARGV(0, float) = WinningCondition_Invasion();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = invasion_teams;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
-{
-       M_ARGV(0, string) = "This command does not work during an invasion!";
-       return true;
-}
-
-void invasion_ScoreRules(int inv_teams)
-{
-       if(inv_teams) { CheckAllowedTeams(NULL); }
-       GameRules_score_enabled(false);
-       GameRules_scoring(inv_teams, 0, 0, {
-           if (inv_teams) {
-            field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
-           }
-           field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
-       });
-}
-
-void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
-       if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
-               cvar_set("fraglimit", "0");
-
-       if(autocvar_g_invasion_teams)
-       {
-               invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
-       }
-       else
-               invasion_teams = 0;
-
-       independent_players = 1; // to disable extra useless scores
-
-       invasion_ScoreRules(invasion_teams);
-
-       independent_players = 0;
-
-       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-       {
-               round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
-               round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
-               inv_roundcnt = 0;
-               inv_maxrounds = 15; // 15?
-       }
-}
-
-void invasion_Initialize()
-{
-       InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh
deleted file mode 100644 (file)
index 85cd7ec..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
-int autocvar_g_invasion_teams;
-int autocvar_g_invasion_type;
-bool autocvar_g_invasion_team_spawns;
-bool g_invasion;
-void invasion_Initialize();
-
-REGISTER_MUTATOR(inv, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               if (autocvar_g_invasion_teams >= 2) {
-                       GameRules_teams(true);
-                       GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
-               }
-        GameRules_limit_score(autocvar_g_invasion_point_limit);
-
-               g_invasion = true;
-               cvar_settemp("g_monsters", "1");
-               invasion_Initialize();
-       }
-       return 0;
-}
-
-float inv_numspawned;
-float inv_maxspawned;
-float inv_roundcnt;
-float inv_maxrounds;
-float inv_numkilled;
-float inv_lastcheck;
-float inv_maxcurrent;
-
-float invasion_teams;
-float inv_monsters_perteam[17];
-
-float inv_monsterskill;
-
-const float ST_INV_KILLS = 1;
-
-const int INV_TYPE_ROUND = 0; // round-based waves of enemies
-const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
-const int INV_TYPE_STAGE = 2; // reach the end of the level
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc b/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc
new file mode 100644 (file)
index 0000000..c9670a1
--- /dev/null
@@ -0,0 +1,603 @@
+#include "sv_invasion.qh"
+
+#include <common/monsters/sv_spawn.qh>
+#include <common/monsters/sv_spawner.qh>
+#include <common/monsters/sv_monsters.qh>
+
+#include <server/teamplay.qh>
+
+IntrusiveList g_invasion_roundends;
+IntrusiveList g_invasion_waves;
+IntrusiveList g_invasion_spawns;
+STATIC_INIT(g_invasion)
+{
+       g_invasion_roundends = IL_NEW();
+       g_invasion_waves = IL_NEW();
+       g_invasion_spawns = IL_NEW();
+}
+
+float autocvar_g_invasion_round_timelimit;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+float autocvar_g_invasion_warmup;
+int autocvar_g_invasion_monster_count;
+bool autocvar_g_invasion_zombies_only;
+float autocvar_g_invasion_spawn_delay;
+
+bool victent_present;
+.bool inv_endreached;
+
+bool inv_warning_shown; // spammy
+
+void target_invasion_roundend_use(entity this, entity actor, entity trigger)
+{
+       if(!IS_PLAYER(actor)) { return; }
+
+       actor.inv_endreached = true;
+
+       int plnum = 0;
+       int realplnum = 0;
+       // let's not count bots
+       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+               ++realplnum;
+               if(it.inv_endreached)
+                       ++plnum;
+       });
+       if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
+               return;
+
+       this.winning = true;
+}
+
+spawnfunc(target_invasion_roundend)
+{
+       if(!g_invasion) { delete(this); return; }
+
+       victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
+
+       if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
+
+       this.use = target_invasion_roundend_use;
+
+       IL_PUSH(g_invasion_roundends, this);
+}
+
+spawnfunc(invasion_wave)
+{
+       if(!g_invasion) { delete(this); return; }
+
+       IL_PUSH(g_invasion_waves, this);
+}
+
+spawnfunc(invasion_spawnpoint)
+{
+       if(!g_invasion) { delete(this); return; }
+
+       this.classname = "invasion_spawnpoint";
+       IL_PUSH(g_invasion_spawns, this);
+}
+
+void ClearWinners();
+
+// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win.
+int WinningCondition_Invasion()
+{
+       WinningConditionHelper(NULL); // set worldstatus
+
+       int status = WINNING_NO;
+
+       if(autocvar_g_invasion_type == INV_TYPE_STAGE)
+       {
+               SetWinners(inv_endreached, true);
+
+               int found = 0;
+               IL_EACH(g_invasion_roundends, true,
+               {
+                       ++found;
+                       if(it.winning)
+                       {
+                               bprint("Invasion: round completed.\n");
+                               // winners already set (TODO: teamplay support)
+
+                               status = WINNING_YES;
+                               break;
+                       }
+               });
+
+               if(!found)
+                       status = WINNING_YES; // just end it? TODO: should warn mapper!
+       }
+       else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+       {
+               ClearWinners();
+
+               int found = 0; // NOTE: this ends the round if no monsters are placed
+               IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
+               {
+                       ++found;
+               });
+
+               if(found <= 0)
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                       {
+                               it.winning = true;
+                       });
+                       status = WINNING_YES;
+               }
+       }
+
+       return status;
+}
+
+Monster invasion_PickMonster(int supermonster_count)
+{
+       RandomSelection_Init();
+
+       FOREACH(Monsters, it != MON_Null,
+       {
+               if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
+                       (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
+                       continue;
+               if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
+                       continue;
+        RandomSelection_AddEnt(it, 1, 1);
+       });
+
+       return RandomSelection_chosen_ent;
+}
+
+entity invasion_PickSpawn()
+{
+       RandomSelection_Init();
+
+       IL_EACH(g_invasion_spawns, true,
+       {
+               RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+               it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+       });
+
+       return RandomSelection_chosen_ent;
+}
+
+entity invasion_GetWaveEntity(int wavenum)
+{
+       IL_EACH(g_invasion_waves, it.cnt == wavenum,
+       {
+               return it; // found one
+       });
+
+       // if no specific one is found, find the last existing wave ent
+       entity best = NULL;
+       IL_EACH(g_invasion_waves, it.cnt <= wavenum,
+       {
+               if(!best || it.cnt > best.cnt)
+                       best = it;
+       });
+
+       return best;
+}
+
+void invasion_SpawnChosenMonster(Monster mon)
+{
+       entity monster;
+       entity spawn_point = invasion_PickSpawn();
+       entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
+
+       string tospawn = "";
+       if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
+       {
+               RandomSelection_Init();
+               FOREACH_WORD(wave_ent.spawnmob, true,
+               {
+                       RandomSelection_AddString(it, 1, 1);
+               });
+
+               tospawn = RandomSelection_chosen_string;
+       }
+
+       if(spawn_point == NULL)
+       {
+               if(!inv_warning_shown)
+               {
+                       inv_warning_shown = true;
+                       LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
+               }
+               entity e = spawn();
+               setsize(e, mon.m_mins, mon.m_maxs);
+
+               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+                       monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
+               else
+               {
+                       delete(e);
+                       return;
+               }
+       }
+       else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
+               monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
+
+       if(!monster)
+               return;
+
+       monster.spawnshieldtime = time;
+
+       if(spawn_point)
+       {
+               if(spawn_point.target_range)
+                       monster.target_range = spawn_point.target_range;
+               monster.target2 = spawn_point.target2;
+       }
+
+       if(teamplay)
+       {
+               if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+                       monster.team = spawn_point.team;
+               else
+               {
+                       RandomSelection_Init();
+                       if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
+                       if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
+                       if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
+                       if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
+
+                       monster.team = RandomSelection_chosen_float;
+               }
+
+               monster_setupcolors(monster);
+
+               if(monster.sprite)
+               {
+                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
+
+                       monster.sprite.team = 0;
+                       monster.sprite.SendFlags |= 1;
+               }
+       }
+
+       if(monster.monster_attack)
+               IL_REMOVE(g_monster_targets, monster);
+       monster.monster_attack = false; // it's the player's job to kill all the monsters
+
+       if(inv_roundcnt >= inv_maxrounds)
+               monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
+}
+
+void invasion_SpawnMonsters(int supermonster_count)
+{
+       Monster chosen_monster = invasion_PickMonster(supermonster_count);
+
+       invasion_SpawnChosenMonster(chosen_monster);
+}
+
+bool Invasion_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               IL_EACH(g_monsters, true,
+               {
+                       Monster_Remove(it);
+               });
+               IL_CLEAR(g_monsters);
+
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+               return 1;
+       }
+
+       float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
+
+       IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
+       {
+               if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+                       ++supermonster_count;
+               ++total_alive_monsters;
+
+               if(teamplay)
+               switch(it.team)
+               {
+                       case NUM_TEAM_1: ++red_alive; break;
+                       case NUM_TEAM_2: ++blue_alive; break;
+                       case NUM_TEAM_3: ++yellow_alive; break;
+                       case NUM_TEAM_4: ++pink_alive; break;
+               }
+       });
+
+       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
+       {
+               if(time >= inv_lastcheck)
+               {
+                       invasion_SpawnMonsters(supermonster_count);
+                       inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
+               }
+
+               return 0;
+       }
+
+       if(inv_numspawned < 1)
+               return 0; // nothing has spawned yet
+
+       if(teamplay)
+       {
+               if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+                       return 0;
+       }
+       else if(inv_numkilled < inv_maxspawned)
+               return 0;
+
+       entity winner = NULL;
+       float winning_score = 0, winner_team = 0;
+
+
+       if(teamplay)
+       {
+               if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+               if(blue_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_2; }
+               if(yellow_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_3; }
+               if(pink_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_4; }
+       }
+       else
+       {
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       float cs = GameRules_scoring_add(it, KILLS, 0);
+                       if(cs > winning_score)
+                       {
+                               winning_score = cs;
+                               winner = it;
+                       }
+               });
+       }
+
+       IL_EACH(g_monsters, true,
+       {
+               Monster_Remove(it);
+       });
+       IL_CLEAR(g_monsters);
+
+       if(teamplay)
+       {
+               if(winner_team)
+               {
+                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               }
+       }
+       else if(winner)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
+       }
+
+       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+       return 1;
+}
+
+bool Invasion_CheckPlayers()
+{
+       return true;
+}
+
+void Invasion_RoundStart()
+{
+       int numplayers = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               it.player_blocked = false;
+               ++numplayers;
+       });
+
+       if(inv_roundcnt < inv_maxrounds)
+               inv_roundcnt += 1; // a limiter to stop crazy counts
+
+       inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
+
+       inv_maxcurrent = 0;
+       inv_numspawned = 0;
+       inv_numkilled = 0;
+
+       inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+       if(teamplay)
+       {
+               DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+               inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+               inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterDies)
+{
+       entity frag_target = M_ARGV(0, entity);
+       entity frag_attacker = M_ARGV(1, entity);
+
+       if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
+       {
+               if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+               {
+                       inv_numkilled += 1;
+                       inv_maxcurrent -= 1;
+               }
+               if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
+
+               if(IS_PLAYER(frag_attacker))
+               if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
+                       GameRules_scoring_add(frag_attacker, KILLS, -1);
+               else
+               {
+                       GameRules_scoring_add(frag_attacker, KILLS, +1);
+                       if(teamplay)
+                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
+{
+       entity mon = M_ARGV(0, entity);
+       mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+       if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+               return false; // allowed
+
+       if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
+               return true;
+
+       if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
+       {
+               inv_numspawned += 1;
+               inv_maxcurrent += 1;
+       }
+
+       mon.monster_skill = inv_monsterskill;
+
+       if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
+}
+
+MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
+{
+       if(autocvar_g_invasion_type != INV_TYPE_ROUND)
+               return; // uses map spawned monsters
+
+       monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+       monsters_killed = inv_numkilled;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
+{
+       // no regeneration in invasion, regardless of the game type
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.bot_attack)
+               IL_REMOVE(g_bot_targets, player);
+       player.bot_attack = false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
+       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
+       {
+               frag_damage = 0;
+               frag_force = '0 0 0';
+
+               M_ARGV(4, float) = frag_damage;
+               M_ARGV(6, vector) = frag_force;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
+{
+       entity targ = M_ARGV(1, entity);
+
+       if(!IS_MONSTER(targ))
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SetStartItems)
+{
+       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+       {
+               start_health = 200;
+               start_armorvalue = 200;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
+{
+       entity frag_target = M_ARGV(1, entity);
+
+       if(IS_MONSTER(frag_target))
+               return MUT_ACCADD_INVALID;
+       return MUT_ACCADD_INDIFFERENT;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
+{
+       // monster spawning disabled during an invasion
+       M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
+{
+       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+               return false;
+
+       M_ARGV(0, float) = WinningCondition_Invasion();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = invasion_teams;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
+{
+       M_ARGV(0, string) = "This command does not work during an invasion!";
+       return true;
+}
+
+void invasion_ScoreRules(int inv_teams)
+{
+       GameRules_score_enabled(false);
+       GameRules_scoring(inv_teams, 0, 0, {
+           if (inv_teams) {
+            field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+           }
+           field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
+       });
+}
+
+void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+       if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
+               cvar_set("fraglimit", "0");
+
+       if(autocvar_g_invasion_teams)
+       {
+               invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
+       }
+       else
+               invasion_teams = 0;
+
+       independent_players = 1; // to disable extra useless scores
+
+       invasion_ScoreRules(invasion_teams);
+
+       independent_players = 0;
+
+       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+       {
+               round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
+               round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+               inv_roundcnt = 0;
+               inv_maxrounds = 15; // 15?
+       }
+}
+
+void invasion_Initialize()
+{
+       InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qh b/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qh
new file mode 100644 (file)
index 0000000..167380e
--- /dev/null
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
+int autocvar_g_invasion_teams;
+int autocvar_g_invasion_type;
+bool autocvar_g_invasion_team_spawns;
+bool g_invasion;
+void invasion_Initialize();
+
+REGISTER_MUTATOR(inv, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               if (autocvar_g_invasion_teams >= 2) {
+                       GameRules_teams(true);
+                       GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
+               }
+        GameRules_limit_score(autocvar_g_invasion_point_limit);
+
+               g_invasion = true;
+               cvar_settemp("g_monsters", "1");
+               invasion_Initialize();
+       }
+       return 0;
+}
+
+float inv_numspawned;
+float inv_maxspawned;
+float inv_roundcnt;
+float inv_maxrounds;
+float inv_numkilled;
+float inv_lastcheck;
+float inv_maxcurrent;
+
+float invasion_teams;
+float inv_monsters_perteam[17];
+
+float inv_monsterskill;
+
+const float ST_INV_KILLS = 1;
+
+const int INV_TYPE_ROUND = 0; // round-based waves of enemies
+const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
+const int INV_TYPE_STAGE = 2; // reach the end of the level
index 9426d78074063808380a788a173a2825a696df6a..420f7af78aa2c29c635e28e9b8113210230f2629 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/keepaway/keepaway.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/keepaway/sv_keepaway.qc>
+#endif
index 32872a2a6bfa9047ecae580813b5a644ac5a7ad8..145ca49f0a06878ab0ca96d381dc0f020c32f896 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/keepaway/keepaway.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/keepaway/sv_keepaway.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc
deleted file mode 100644 (file)
index c8bfbf2..0000000
+++ /dev/null
@@ -1,475 +0,0 @@
-#include "keepaway.qh"
-
-// TODO: keepaway
-#ifdef SVQC
-#include <common/effects/all.qh>
-
-.entity ballcarried;
-
-int autocvar_g_keepaway_ballcarrier_effects;
-float autocvar_g_keepaway_ballcarrier_damage;
-float autocvar_g_keepaway_ballcarrier_force;
-float autocvar_g_keepaway_ballcarrier_highspeed;
-float autocvar_g_keepaway_ballcarrier_selfdamage;
-float autocvar_g_keepaway_ballcarrier_selfforce;
-float autocvar_g_keepaway_noncarrier_damage;
-float autocvar_g_keepaway_noncarrier_force;
-float autocvar_g_keepaway_noncarrier_selfdamage;
-float autocvar_g_keepaway_noncarrier_selfforce;
-bool autocvar_g_keepaway_noncarrier_warn;
-int autocvar_g_keepaway_score_bckill;
-int autocvar_g_keepaway_score_killac;
-int autocvar_g_keepaway_score_timepoints;
-float autocvar_g_keepaway_score_timeinterval;
-float autocvar_g_keepawayball_damageforcescale;
-int autocvar_g_keepawayball_effects;
-float autocvar_g_keepawayball_respawntime;
-int autocvar_g_keepawayball_trail_color;
-
-bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
-{
-       if(view.ballcarried)
-               if(IS_SPEC(player))
-                       return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
-
-       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
-
-       return true;
-}
-
-void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ka_TouchEvent(entity this, entity toucher);
-void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
-{
-       if(game_stopped) return;
-       vector oldballorigin = this.origin;
-
-       if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
-       {
-               entity spot = SelectSpawnPoint(this, true);
-               setorigin(this, spot.origin);
-               this.angles = spot.angles;
-       }
-
-       makevectors(this.angles);
-       set_movetype(this, MOVETYPE_BOUNCE);
-       this.velocity = '0 0 200';
-       this.angles = '0 0 0';
-       this.effects = autocvar_g_keepawayball_effects;
-       settouch(this, ka_TouchEvent);
-       setthink(this, ka_RespawnBall);
-       this.nextthink = time + autocvar_g_keepawayball_respawntime;
-       navigation_dynamicgoal_set(this);
-
-       Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
-       Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
-
-       WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
-       WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
-
-       sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-}
-
-void ka_TimeScoring(entity this)
-{
-       if(this.owner.ballcarried)
-       { // add points for holding the ball after a certain amount of time
-               if(autocvar_g_keepaway_score_timepoints)
-                       GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
-
-               GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
-               this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
-       }
-}
-
-void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
-{
-       if(game_stopped) return;
-       if(!this) return;
-       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       { // The ball fell off the map, respawn it since players can't get to it
-               ka_RespawnBall(this);
-               return;
-       }
-       if(IS_DEAD(toucher)) { return; }
-       if(STAT(FROZEN, toucher)) { return; }
-       if (!IS_PLAYER(toucher))
-       {  // The ball just touched an object, most likely the world
-               Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
-               sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
-               return;
-       }
-       else if(this.wait > time) { return; }
-
-       // attach the ball to the player
-       this.owner = toucher;
-       toucher.ballcarried = this;
-       GameRules_scoring_vip(toucher, true);
-       setattachment(this, toucher, "");
-       setorigin(this, '0 0 0');
-
-       // make the ball invisible/unable to do anything/set up time scoring
-       this.velocity = '0 0 0';
-       set_movetype(this, MOVETYPE_NONE);
-       this.effects |= EF_NODRAW;
-       settouch(this, func_null);
-       setthink(this, ka_TimeScoring);
-       this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
-       this.takedamage = DAMAGE_NO;
-       navigation_dynamicgoal_unset(this);
-
-       // apply effects to player
-       toucher.glow_color = autocvar_g_keepawayball_trail_color;
-       toucher.glow_trail = true;
-       toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
-
-       // messages and sounds
-       ka_EventLog("pickup", toucher);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
-       Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
-       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
-       sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
-       // scoring
-       GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
-
-       // waypoints
-       WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
-       toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
-       WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
-       WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
-       WaypointSprite_Kill(this.waypointsprite_attachedforcarrier);
-}
-
-void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
-{
-       entity ball;
-       ball = plyr.ballcarried;
-
-       if(!ball) { return; }
-
-       // reset the ball
-       setattachment(ball, NULL, "");
-       set_movetype(ball, MOVETYPE_BOUNCE);
-       ball.wait = time + 1;
-       settouch(ball, ka_TouchEvent);
-       setthink(ball, ka_RespawnBall);
-       ball.nextthink = time + autocvar_g_keepawayball_respawntime;
-       ball.takedamage = DAMAGE_YES;
-       ball.effects &= ~EF_NODRAW;
-       setorigin(ball, plyr.origin + '0 0 10');
-       ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
-       entity e = ball.owner; ball.owner = NULL;
-       e.ballcarried = NULL;
-       GameRules_scoring_vip(e, false);
-       navigation_dynamicgoal_set(ball);
-
-       // reset the player effects
-       plyr.glow_trail = false;
-       plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
-       // messages and sounds
-       ka_EventLog("dropped", plyr);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
-       sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
-       // scoring
-       // GameRules_scoring_add(plyr, KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless.
-
-       // waypoints
-       WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
-       WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
-       WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
-       WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
-}
-
-/** used to clear the ballcarrier whenever the match switches from warmup to normal */
-void ka_Reset(entity this)
-{
-       if((this.owner) && (IS_PLAYER(this.owner)))
-               ka_DropEvent(this.owner);
-
-       if(time < game_starttime)
-       {
-               setthink(this, ka_RespawnBall);
-               settouch(this, func_null);
-               this.nextthink = game_starttime;
-       }
-       else
-               ka_RespawnBall(this);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
-{
-       float t;
-       entity ball_owner;
-       ball_owner = ka_ball.owner;
-
-       if (ball_owner == this)
-               return;
-
-       // If ball is carried by player then hunt them down.
-       if (ball_owner)
-       {
-               t = (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) / (GetResourceAmount(ball_owner, RESOURCE_HEALTH) + GetResourceAmount(ball_owner, RESOURCE_ARMOR));
-               navigation_routerating(this, ball_owner, t * ratingscale, 2000);
-       }
-       else // Ball has been dropped so collect.
-               navigation_routerating(this, ka_ball, ratingscale, 2000);
-}
-
-void havocbot_role_ka_carrier(entity this)
-{
-       if (IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-
-       if (!this.ballcarried)
-       {
-               this.havocbot_role = havocbot_role_ka_collector;
-               navigation_goalrating_timeout_expire(this, 2);
-       }
-}
-
-void havocbot_role_ka_collector(entity this)
-{
-       if (IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
-               havocbot_goalrating_ball(this, 20000, this.origin);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-
-       if (this.ballcarried)
-       {
-               this.havocbot_role = havocbot_role_ka_carrier;
-               navigation_goalrating_timeout_expire(this, 2);
-       }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ka, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
-       {
-               if(frag_target.ballcarried) { // add to amount of times killing carrier
-                       GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
-                       if(autocvar_g_keepaway_score_bckill) // add bckills to the score
-                               GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_bckill);
-               }
-               else if(!frag_attacker.ballcarried)
-                       if(autocvar_g_keepaway_noncarrier_warn)
-                               Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN);
-
-               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
-                       GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
-       }
-
-       if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
-{
-       M_ARGV(2, float) = 0; // no frags counted in keepaway
-       return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       // clear the item used for the ball in keepaway
-       player.items &= ~IT_KEY1;
-
-       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
-       if(player.ballcarried)
-               player.items |= IT_KEY1;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(MUTATOR_RETURNVALUE == 0)
-       if(player.ballcarried)
-       {
-               ka_DropEvent(player);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(4, float);
-       vector frag_force = M_ARGV(6, vector);
-
-       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
-       {
-               if(frag_target == frag_attacker) // damage done to yourself
-               {
-                       frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
-                       frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
-               }
-               else // damage done to noncarriers
-               {
-                       frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
-                       frag_force *= autocvar_g_keepaway_ballcarrier_force;
-               }
-       }
-       else if (!frag_target.ballcarried) // if the target is a noncarrier
-       {
-               if(frag_target == frag_attacker) // damage done to yourself
-               {
-                       frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
-                       frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
-               }
-               else // damage done to other noncarriers
-               {
-                       frag_damage *= autocvar_g_keepaway_noncarrier_damage;
-                       frag_force *= autocvar_g_keepaway_noncarrier_force;
-               }
-       }
-
-       M_ARGV(4, float) = frag_damage;
-       M_ARGV(6, vector) = frag_force;
-}
-
-MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
-{
-       entity player = M_ARGV(0, entity);
-
-       // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
-       // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player()
-
-       player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
-       if(player.ballcarried)
-               player.effects |= autocvar_g_keepaway_ballcarrier_effects;
-}
-
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
-{
-       entity player = M_ARGV(0, entity);
-       // these automatically reset, no need to worry
-
-       if(player.ballcarried)
-               STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
-}
-
-MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
-{
-       entity bot = M_ARGV(0, entity);
-       entity targ = M_ARGV(1, entity);
-
-       // if neither player has ball then don't attack unless the ball is on the ground
-       if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       if (bot.ballcarried)
-               bot.havocbot_role = havocbot_role_ka_carrier;
-       else
-               bot.havocbot_role = havocbot_role_ka_collector;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
-{
-       entity frag_target = M_ARGV(0, entity);
-
-       if(frag_target.ballcarried)
-               ka_DropEvent(frag_target);
-}
-
-.bool pushable;
-
-// ==============
-// Initialization
-// ==============
-
-MODEL(KA_BALL, "models/orbs/orbblue.md3");
-
-void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
-{
-       entity e = new(keepawayball);
-       setmodel(e, MDL_KA_BALL);
-       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
-       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
-       e.takedamage = DAMAGE_YES;
-       e.solid = SOLID_TRIGGER;
-       set_movetype(e, MOVETYPE_BOUNCE);
-       e.glow_color = autocvar_g_keepawayball_trail_color;
-       e.glow_trail = true;
-       e.flags = FL_ITEM;
-       IL_PUSH(g_items, e);
-       e.pushable = true;
-       e.reset = ka_Reset;
-       settouch(e, ka_TouchEvent);
-       e.owner = NULL;
-       ka_ball = e;
-       navigation_dynamicgoal_init(ka_ball, false);
-
-       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
-}
-
-void ka_Initialize() // run at the start of a match, initiates game mode
-{
-       ka_SpawnBall();
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh
deleted file mode 100644 (file)
index a4615c1..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <common/scores.qh>
-void ka_Initialize();
-
-REGISTER_MUTATOR(ka, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-           GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
-            field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
-            field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
-            field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
-        });
-
-               ka_Initialize();
-       }
-       return false;
-}
-
-
-entity ka_ball;
-
-void(entity this) havocbot_role_ka_carrier;
-void(entity this) havocbot_role_ka_collector;
-
-void ka_DropEvent(entity plyr);
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc
new file mode 100644 (file)
index 0000000..66acc2e
--- /dev/null
@@ -0,0 +1,479 @@
+#include "sv_keepaway.qh"
+
+#include <common/effects/all.qh>
+
+.entity ballcarried;
+
+int autocvar_g_keepaway_ballcarrier_effects;
+float autocvar_g_keepaway_ballcarrier_damage;
+float autocvar_g_keepaway_ballcarrier_force;
+float autocvar_g_keepaway_ballcarrier_highspeed;
+float autocvar_g_keepaway_ballcarrier_selfdamage;
+float autocvar_g_keepaway_ballcarrier_selfforce;
+float autocvar_g_keepaway_noncarrier_damage;
+float autocvar_g_keepaway_noncarrier_force;
+float autocvar_g_keepaway_noncarrier_selfdamage;
+float autocvar_g_keepaway_noncarrier_selfforce;
+bool autocvar_g_keepaway_noncarrier_warn;
+int autocvar_g_keepaway_score_bckill;
+int autocvar_g_keepaway_score_killac;
+int autocvar_g_keepaway_score_timepoints;
+float autocvar_g_keepaway_score_timeinterval;
+float autocvar_g_keepawayball_damageforcescale;
+int autocvar_g_keepawayball_effects;
+float autocvar_g_keepawayball_respawntime;
+int autocvar_g_keepawayball_trail_color;
+
+bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
+{
+       if(view.ballcarried)
+               if(IS_SPEC(player))
+                       return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
+
+       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
+
+       return true;
+}
+
+void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ka_TouchEvent(entity this, entity toucher);
+void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
+{
+       if(game_stopped) return;
+       vector oldballorigin = this.origin;
+
+       if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+       {
+               entity spot = SelectSpawnPoint(this, true);
+               setorigin(this, spot.origin);
+               this.angles = spot.angles;
+       }
+
+       makevectors(this.angles);
+       set_movetype(this, MOVETYPE_BOUNCE);
+       this.velocity = '0 0 200';
+       this.angles = '0 0 0';
+       this.effects = autocvar_g_keepawayball_effects;
+       settouch(this, ka_TouchEvent);
+       setthink(this, ka_RespawnBall);
+       this.nextthink = time + autocvar_g_keepawayball_respawntime;
+       navigation_dynamicgoal_set(this, NULL);
+
+       Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
+       Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
+
+       WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+       WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
+
+       sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void ka_TimeScoring(entity this)
+{
+       if(this.owner.ballcarried)
+       { // add points for holding the ball after a certain amount of time
+               if(autocvar_g_keepaway_score_timepoints)
+                       GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
+
+               GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
+               this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       }
+}
+
+void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
+{
+       if (!this || game_stopped)
+               return;
+
+       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+       { // The ball fell off the map, respawn it since players can't get to it
+               ka_RespawnBall(this);
+               return;
+       }
+       if(IS_DEAD(toucher)) { return; }
+       if(STAT(FROZEN, toucher)) { return; }
+       if (!IS_PLAYER(toucher))
+       {  // The ball just touched an object, most likely the world
+               Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
+               sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
+               return;
+       }
+       else if(this.wait > time) { return; }
+
+       // attach the ball to the player
+       this.owner = toucher;
+       toucher.ballcarried = this;
+       GameRules_scoring_vip(toucher, true);
+       setattachment(this, toucher, "");
+       setorigin(this, '0 0 0');
+
+       // make the ball invisible/unable to do anything/set up time scoring
+       this.velocity = '0 0 0';
+       set_movetype(this, MOVETYPE_NONE);
+       this.effects |= EF_NODRAW;
+       settouch(this, func_null);
+       setthink(this, ka_TimeScoring);
+       this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       this.takedamage = DAMAGE_NO;
+       navigation_dynamicgoal_unset(this);
+
+       // apply effects to player
+       toucher.glow_color = autocvar_g_keepawayball_trail_color;
+       toucher.glow_trail = true;
+       toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
+
+       // messages and sounds
+       ka_EventLog("pickup", toucher);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
+       Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
+       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
+       sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+       // scoring
+       GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
+
+       // waypoints
+       WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
+       toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+       WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
+       WaypointSprite_Kill(this.waypointsprite_attachedforcarrier);
+}
+
+void ka_PlayerReset(entity plyr)
+{
+       plyr.ballcarried = NULL;
+       GameRules_scoring_vip(plyr, false);
+       WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
+
+       // reset the player effects
+       plyr.glow_trail = false;
+       plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+}
+
+void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
+{
+       entity ball;
+       ball = plyr.ballcarried;
+
+       if(!ball) { return; }
+
+       // reset the ball
+       setattachment(ball, NULL, "");
+       set_movetype(ball, MOVETYPE_BOUNCE);
+       ball.wait = time + 1;
+       settouch(ball, ka_TouchEvent);
+       setthink(ball, ka_RespawnBall);
+       ball.nextthink = time + autocvar_g_keepawayball_respawntime;
+       ball.takedamage = DAMAGE_YES;
+       ball.effects &= ~EF_NODRAW;
+       setorigin(ball, plyr.origin + '0 0 10');
+       ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+       ball.owner = NULL;
+       navigation_dynamicgoal_set(ball, plyr);
+
+       // messages and sounds
+       ka_EventLog("dropped", plyr);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
+       sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+       // waypoints
+       WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+       WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
+
+       ka_PlayerReset(plyr);
+}
+
+.bool pushable;
+
+MODEL(KA_BALL, "models/orbs/orbblue.md3");
+
+void ka_RemoveBall()
+{
+       entity plyr = ka_ball.owner;
+       if (plyr) // it was attached
+               ka_PlayerReset(plyr);
+       else
+               WaypointSprite_DetachCarrier(ka_ball);
+       delete(ka_ball);
+       ka_ball = NULL;
+}
+
+void ka_SpawnBall()
+{
+       entity e = new(keepawayball);
+       setmodel(e, MDL_KA_BALL);
+       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+       e.takedamage = DAMAGE_YES;
+       e.solid = SOLID_TRIGGER;
+       set_movetype(e, MOVETYPE_BOUNCE);
+       e.glow_color = autocvar_g_keepawayball_trail_color;
+       e.glow_trail = true;
+       e.flags = FL_ITEM;
+       IL_PUSH(g_items, e);
+       e.pushable = true;
+       settouch(e, ka_TouchEvent);
+       e.owner = NULL;
+       ka_ball = e;
+       navigation_dynamicgoal_init(ka_ball, false);
+
+       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
+}
+
+void ka_Handler_CheckBall(entity this)
+{
+       if(time < game_starttime)
+       {
+               if (ka_ball)
+                       ka_RemoveBall();
+       }
+       else
+       {
+               if (!ka_ball)
+                       ka_SpawnBall();
+       }
+
+       this.nextthink = time;
+}
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+       ka_Handler = new(ka_Handler);
+       setthink(ka_Handler, ka_Handler_CheckBall);
+       ka_Handler.nextthink = time;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
+{
+       entity ball_owner;
+       ball_owner = ka_ball.owner;
+
+       if (ball_owner == this)
+               return;
+
+       if (ball_owner)
+               navigation_routerating(this, ball_owner, ratingscale, 2000);
+       else
+               navigation_routerating(this, ka_ball, ratingscale, 2000);
+}
+
+void havocbot_role_ka_carrier(entity this)
+{
+       if (IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+
+       if (!this.ballcarried)
+       {
+               this.havocbot_role = havocbot_role_ka_collector;
+               navigation_goalrating_timeout_expire(this, 2);
+       }
+}
+
+void havocbot_role_ka_collector(entity this)
+{
+       if (IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 500, this.origin, 10000);
+               havocbot_goalrating_ball(this, 8000, this.origin);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+
+       if (this.ballcarried)
+       {
+               this.havocbot_role = havocbot_role_ka_carrier;
+               navigation_goalrating_timeout_expire(this, 2);
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ka, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
+       {
+               if(frag_target.ballcarried) { // add to amount of times killing carrier
+                       GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
+                       if(autocvar_g_keepaway_score_bckill) // add bckills to the score
+                               GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_bckill);
+               }
+               else if(!frag_attacker.ballcarried)
+                       if(autocvar_g_keepaway_noncarrier_warn)
+                               Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN);
+
+               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
+                       GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
+       }
+
+       if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
+{
+       M_ARGV(2, float) = 0; // no frags counted in keepaway
+       return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       // clear the item used for the ball in keepaway
+       player.items &= ~IT_KEY1;
+
+       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+       if(player.ballcarried)
+               player.items |= IT_KEY1;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(MUTATOR_RETURNVALUE == 0)
+       if(player.ballcarried)
+       {
+               ka_DropEvent(player);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
+       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
+               }
+               else // damage done to noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_force;
+               }
+       }
+       else if (!frag_target.ballcarried) // if the target is a noncarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
+               }
+               else // damage done to other noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_noncarrier_damage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_force;
+               }
+       }
+
+       M_ARGV(4, float) = frag_damage;
+       M_ARGV(6, vector) = frag_force;
+}
+
+MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
+{
+       entity player = M_ARGV(0, entity);
+
+       // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
+       // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player()
+
+       player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+
+       if(player.ballcarried)
+               player.effects |= autocvar_g_keepaway_ballcarrier_effects;
+}
+
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
+{
+       entity player = M_ARGV(0, entity);
+       // these automatically reset, no need to worry
+
+       if(player.ballcarried)
+               STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
+}
+
+MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
+{
+       entity bot = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       // if neither player has ball then don't attack unless the ball is on the ground
+       if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       if (bot.ballcarried)
+               bot.havocbot_role = havocbot_role_ka_carrier;
+       else
+               bot.havocbot_role = havocbot_role_ka_collector;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
+{
+       entity frag_target = M_ARGV(0, entity);
+
+       if(frag_target.ballcarried)
+               ka_DropEvent(frag_target);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qh b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qh
new file mode 100644 (file)
index 0000000..3c14c89
--- /dev/null
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+void ka_Initialize();
+
+REGISTER_MUTATOR(ka, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+           GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+            field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
+            field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
+            field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
+        });
+
+               ka_Initialize();
+       }
+       return false;
+}
+
+
+entity ka_ball;
+entity ka_Handler;
+
+void(entity this) havocbot_role_ka_carrier;
+void(entity this) havocbot_role_ka_collector;
+
+void ka_DropEvent(entity plyr);
index 3861dea00600a149bc60f2e9dc8ec7161505a563..4f44840c41c7ba592ac0679917fd767aa379adbf 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/keyhunt/keyhunt.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc>
+#endif
index cd796c7ef9c92988c5c9393479bdf7771b502135..e4143fc84cd91165519803a90f145f7977a841fe 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/keyhunt/keyhunt.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/keyhunt/sv_keyhunt.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc
deleted file mode 100644 (file)
index 6523612..0000000
+++ /dev/null
@@ -1,1324 +0,0 @@
-#include "keyhunt.qh"
-
-// TODO: sv_keyhunt
-#ifdef SVQC
-float autocvar_g_balance_keyhunt_damageforcescale;
-float autocvar_g_balance_keyhunt_delay_collect;
-float autocvar_g_balance_keyhunt_delay_damage_return;
-float autocvar_g_balance_keyhunt_delay_return;
-float autocvar_g_balance_keyhunt_delay_round;
-float autocvar_g_balance_keyhunt_delay_tracking;
-float autocvar_g_balance_keyhunt_return_when_unreachable;
-float autocvar_g_balance_keyhunt_dropvelocity;
-float autocvar_g_balance_keyhunt_maxdist;
-float autocvar_g_balance_keyhunt_protecttime;
-
-int autocvar_g_balance_keyhunt_score_capture;
-int autocvar_g_balance_keyhunt_score_carrierfrag;
-int autocvar_g_balance_keyhunt_score_collect;
-int autocvar_g_balance_keyhunt_score_destroyed;
-int autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-int autocvar_g_balance_keyhunt_score_push;
-float autocvar_g_balance_keyhunt_throwvelocity;
-
-//int autocvar_g_keyhunt_teams;
-int autocvar_g_keyhunt_teams_override;
-
-// #define KH_PLAYER_USE_ATTACHMENT
-// #define KH_PLAYER_USE_CARRIEDMODEL
-
-#ifdef KH_PLAYER_USE_ATTACHMENT
-const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
-const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
-const vector KH_PLAYER_ATTACHMENT = '0 0 0';
-const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
-const string KH_PLAYER_ATTACHMENT_BONE = "";
-#else
-const float KH_KEY_ZSHIFT = 22;
-const float KH_KEY_XYDIST = 24;
-const float KH_KEY_XYSPEED = 45;
-#endif
-const float KH_KEY_WP_ZSHIFT = 20;
-
-const vector KH_KEY_MIN = '-10 -10 -46';
-const vector KH_KEY_MAX = '10 10 3';
-const float KH_KEY_BRIGHTNESS = 2;
-
-bool kh_no_radar_circles;
-
-// kh_state
-//     bits  0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
-//     bits  5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
-//     bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
-//     bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
-.float siren_time;  //  time delay the siren
-//.float stuff_time;  //  time delay to stuffcmd a cvar
-
-int kh_keystatus[17];
-//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player #
-//replace 17 with cvar("maxplayers") or similar !!!!!!!!!
-//for(i = 0; i < maxplayers; ++i)
-//     kh_keystatus[i] = "0";
-
-int kh_Team_ByID(int t)
-{
-       if(t == 0) return NUM_TEAM_1;
-       if(t == 1) return NUM_TEAM_2;
-       if(t == 2) return NUM_TEAM_3;
-       if(t == 3) return NUM_TEAM_4;
-       return 0;
-}
-
-//entity kh_worldkeylist;
-.entity kh_worldkeynext;
-entity kh_controller;
-//bool kh_tracking_enabled;
-int kh_teams;
-int kh_interferemsg_team;
-float kh_interferemsg_time;
-.entity kh_next, kh_prev; // linked list
-.float kh_droptime;
-.int kh_dropperteam;
-.entity kh_previous_owner;
-.int kh_previous_owner_playerid;
-
-int kh_key_dropped, kh_key_carried;
-
-int kh_Key_AllOwnedByWhichTeam();
-
-const int ST_KH_CAPS = 1;
-void kh_ScoreRules(int teams)
-{
-       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
-        field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
-        field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
-        field(SP_KH_PUSHES, "pushes", 0);
-        field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
-        field(SP_KH_PICKUPS, "pickups", 0);
-        field(SP_KH_KCKILLS, "kckills", 0);
-        field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
-       });
-}
-
-bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view)  // runs all the time
-{
-       if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
-               if(!kh_tracking_enabled)
-                       return false;
-
-       return true;
-}
-
-bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
-{
-       if(!kh_tracking_enabled)
-               return false;
-       if(!this.owner)
-               return true;
-       if(!this.owner.owner)
-               return true;
-       return false;  // draw only when key is not owned
-}
-
-void kh_update_state()
-{
-       entity key;
-       int f;
-       int s = 0;
-       FOR_EACH_KH_KEY(key)
-       {
-               if(key.owner)
-                       f = key.team;
-               else
-                       f = 30;
-               s |= (32 ** key.count) * f;
-       }
-
-       FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
-
-       FOR_EACH_KH_KEY(key)
-       {
-               if(key.owner)
-                       STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
-       }
-       //print(ftos((nextent(NULL)).kh_state), "\n");
-}
-
-
-
-
-var kh_Think_t kh_Controller_Thinkfunc;
-void kh_Controller_SetThink(float t, kh_Think_t func)  // runs occasionaly
-{
-       kh_Controller_Thinkfunc = func;
-       kh_controller.cnt = ceil(t);
-       if(t == 0)
-               kh_controller.nextthink = time; // force
-}
-void kh_WaitForPlayers();
-void kh_Controller_Think(entity this)  // called a lot
-{
-       if(game_stopped)
-               return;
-       if(this.cnt > 0)
-       {
-               if(getthink(this) != kh_WaitForPlayers)
-                       this.cnt -= 1;
-       }
-       else if(this.cnt == 0)
-       {
-               this.cnt -= 1;
-               kh_Controller_Thinkfunc();
-       }
-       this.nextthink = time + 1;
-}
-
-// frags f: take from cvar * f
-// frags 0: no frags
-void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)  // update the score when a key is captured
-{
-       string s;
-       if(game_stopped)
-               return;
-
-       if(frags_player)
-               UpdateFrags(player, frags_player);
-
-       if(key && key.owner && frags_owner)
-               UpdateFrags(key.owner, frags_owner);
-
-       if(!autocvar_sv_eventlog)  //output extra info to the console or text file
-               return;
-
-       s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
-
-       if(key && key.owner)
-               s = strcat(s, ":", ftos(key.owner.playerid));
-       else
-               s = strcat(s, ":0");
-
-       s = strcat(s, ":", ftos(frags_owner), ":");
-
-       if(key)
-               s = strcat(s, key.netname);
-
-       GameLogEcho(s);
-}
-
-vector kh_AttachedOrigin(entity e)  // runs when a team captures the flag, it can run 2 or 3 times.
-{
-       if(e.tag_entity)
-       {
-               makevectors(e.tag_entity.angles);
-               return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
-       }
-       else
-               return e.origin;
-}
-
-void kh_Key_Attach(entity key)  // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
-{
-#ifdef KH_PLAYER_USE_ATTACHMENT
-       entity first = key.owner.kh_next;
-       if(key == first)
-       {
-               setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
-               if(key.kh_next)
-               {
-                       setattachment(key.kh_next, key, "");
-                       setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-                       setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
-                       key.kh_next.angles = '0 0 0';
-               }
-               else
-                       setorigin(key, KH_PLAYER_ATTACHMENT);
-               key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
-       }
-       else
-       {
-               setattachment(key, key.kh_prev, "");
-               if(key.kh_next)
-                       setattachment(key.kh_next, key, "");
-               setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
-               setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-               key.angles = '0 0 0';
-       }
-#else
-       setattachment(key, key.owner, "");
-       setorigin(key, '0 0 1' * KH_KEY_ZSHIFT);  // fixing x, y in think
-       key.angles_y -= key.owner.angles.y;
-#endif
-       key.flags = 0;
-       if(IL_CONTAINS(g_items, key))
-               IL_REMOVE(g_items, key);
-       key.solid = SOLID_NOT;
-       set_movetype(key, MOVETYPE_NONE);
-       key.team = key.owner.team;
-       key.nextthink = time;
-       key.damageforcescale = 0;
-       key.takedamage = DAMAGE_NO;
-       key.modelindex = kh_key_carried;
-       navigation_dynamicgoal_unset(key);
-}
-
-void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
-{
-#ifdef KH_PLAYER_USE_ATTACHMENT
-       entity first = key.owner.kh_next;
-       if(key == first)
-       {
-               if(key.kh_next)
-               {
-                       setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
-                       setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-                       key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
-               }
-       }
-       else
-       {
-               if(key.kh_next)
-                       setattachment(key.kh_next, key.kh_prev, "");
-               setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-       }
-       // in any case:
-       setattachment(key, NULL, "");
-       setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
-       key.angles = key.owner.angles;
-#else
-       setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
-       setattachment(key, NULL, "");
-       key.angles_y += key.owner.angles.y;
-#endif
-       key.flags = FL_ITEM;
-       if(!IL_CONTAINS(g_items, key))
-               IL_PUSH(g_items, key);
-       key.solid = SOLID_TRIGGER;
-       set_movetype(key, MOVETYPE_TOSS);
-       key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
-       key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
-       key.takedamage = DAMAGE_YES;
-       // let key.team stay
-       key.modelindex = kh_key_dropped;
-       navigation_dynamicgoal_set(key);
-       key.kh_previous_owner = key.owner;
-       key.kh_previous_owner_playerid = key.owner.playerid;
-}
-
-void kh_Key_AssignTo(entity key, entity player)  // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
-{
-       if(key.owner == player)
-               return;
-
-       int ownerteam0 = kh_Key_AllOwnedByWhichTeam();
-
-       if(key.owner)
-       {
-               kh_Key_Detach(key);
-
-               // remove from linked list
-               if(key.kh_next)
-                       key.kh_next.kh_prev = key.kh_prev;
-               key.kh_prev.kh_next = key.kh_next;
-               key.kh_next = NULL;
-               key.kh_prev = NULL;
-
-               if(key.owner.kh_next == NULL)
-               {
-                       // No longer a key carrier
-                       if(!kh_no_radar_circles)
-                               WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
-                       WaypointSprite_DetachCarrier(key.owner);
-               }
-       }
-
-       key.owner = player;
-
-       if(player)
-       {
-               // insert into linked list
-               key.kh_next = player.kh_next;
-               key.kh_prev = player;
-               player.kh_next = key;
-               if(key.kh_next)
-                       key.kh_next.kh_prev = key;
-
-               float i;
-               i = kh_keystatus[key.owner.playerid];
-                       if(key.netname == "^1red key")
-                               i += 1;
-                       if(key.netname == "^4blue key")
-                               i += 2;
-                       if(key.netname == "^3yellow key")
-                               i += 4;
-                       if(key.netname == "^6pink key")
-                               i += 8;
-               kh_keystatus[key.owner.playerid] = i;
-
-               kh_Key_Attach(key);
-
-               if(key.kh_next == NULL)
-               {
-                       // player is now a key carrier
-                       entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
-                       wp.colormod = colormapPaletteColor(player.team - 1, 0);
-                       player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
-                       WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
-                       if(player.team == NUM_TEAM_1)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
-                       else if(player.team == NUM_TEAM_2)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
-                       else if(player.team == NUM_TEAM_3)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
-                       else if(player.team == NUM_TEAM_4)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
-                       if(!kh_no_radar_circles)
-                               WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
-               }
-       }
-
-       // moved that here, also update if there's no player
-       kh_update_state();
-
-       key.pusher = NULL;
-
-       int ownerteam = kh_Key_AllOwnedByWhichTeam();
-       if(ownerteam != ownerteam0)
-       {
-               entity k;
-               if(ownerteam != -1)
-               {
-                       kh_interferemsg_time = time + 0.2;
-                       kh_interferemsg_team = player.team;
-
-                       // audit all key carrier sprites, update them to "Run here"
-                       FOR_EACH_KH_KEY(k)
-                       {
-                               if (!k.owner) continue;
-                               entity first = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
-                               entity third = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
-                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
-                       }
-               }
-               else
-               {
-                       kh_interferemsg_time = 0;
-
-                       // audit all key carrier sprites, update them to "Key Carrier"
-                       FOR_EACH_KH_KEY(k)
-                       {
-                               if (!k.owner) continue;
-                               entity first = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
-                               entity third = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
-                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
-                       }
-               }
-       }
-}
-
-void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
-       if(this.owner)
-               return;
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
-               return;
-       }
-       if(force == '0 0 0')
-               return;
-       if(time > this.pushltime)
-               if(IS_PLAYER(attacker))
-                       this.team = attacker.team;
-}
-
-void kh_Key_Collect(entity key, entity player)  //a player picks up a dropped key
-{
-       sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
-
-       if(key.kh_dropperteam != player.team)
-       {
-               kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0);
-               GameRules_scoring_add(player, KH_PICKUPS, 1);
-       }
-       key.kh_dropperteam = 0;
-       int realteam = kh_Team_ByID(key.count);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
-
-       kh_Key_AssignTo(key, player); // this also updates .kh_state
-}
-
-void kh_Key_Touch(entity this, entity toucher)  // runs many, many times when a key has been dropped and can be picked up
-{
-       if(game_stopped)
-               return;
-
-       if(this.owner) // already carried
-               return;
-
-       if(ITEM_TOUCH_NEEDKILL())
-       {
-               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
-               return;
-       }
-
-       if (!IS_PLAYER(toucher))
-               return;
-       if(IS_DEAD(toucher))
-               return;
-       if(toucher == this.enemy)
-               if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
-                       return;  // you just dropped it!
-       kh_Key_Collect(this, toucher);
-}
-
-void kh_Key_Remove(entity key)  // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
-{
-       entity o = key.owner;
-       kh_Key_AssignTo(key, NULL);
-       if(o) // it was attached
-               WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
-       else // it was dropped
-               WaypointSprite_DetachCarrier(key);
-
-       // remove key from key list
-       if (kh_worldkeylist == key)
-               kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
-       else
-       {
-               o = kh_worldkeylist;
-               while (o)
-               {
-                       if (o.kh_worldkeynext == key)
-                       {
-                               o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
-                               break;
-                       }
-                       o = o.kh_worldkeynext;
-               }
-       }
-
-       delete(key);
-
-       kh_update_state();
-}
-
-void kh_FinishRound()  // runs when a team captures the keys
-{
-       // prepare next round
-       kh_interferemsg_time = 0;
-       entity key;
-
-       kh_no_radar_circles = true;
-       FOR_EACH_KH_KEY(key)
-               kh_Key_Remove(key);
-       kh_no_radar_circles = false;
-
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
-       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-}
-
-void nades_GiveBonus(entity player, float score);
-
-void kh_WinnerTeam(int winner_team)  // runs when a team wins
-{
-       // all key carriers get some points
-       entity key;
-       float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
-       DistributeEvenly_Init(score, NumTeams(kh_teams));
-       // twice the score for 3 team games, three times the score for 4 team games!
-       // note: for a win by destroying the key, this should NOT be applied
-       FOR_EACH_KH_KEY(key)
-       {
-               float f = DistributeEvenly_Get(1);
-               kh_Scores_Event(key.owner, key, "capture", f, 0);
-               GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
-               nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
-       }
-
-       bool first = true;
-       string keyowner = "";
-       FOR_EACH_KH_KEY(key)
-               if(key.owner.kh_next == key)
-               {
-                       if(!first)
-                               keyowner = strcat(keyowner, ", ");
-                       keyowner = key.owner.netname;
-                       first = false;
-               }
-
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
-
-       first = true;
-       vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
-       FOR_EACH_KH_KEY(key)
-       {
-               vector thisorigin = kh_AttachedOrigin(key);
-               //dprint("Key origin: ", vtos(thisorigin), "\n");
-               midpoint += thisorigin;
-
-               if(!first)
-                       te_lightning2(NULL, lastorigin, thisorigin);
-               lastorigin = thisorigin;
-               if(first)
-                       firstorigin = thisorigin;
-               first = false;
-       }
-       if(NumTeams(kh_teams) > 2)
-       {
-               te_lightning2(NULL, lastorigin, firstorigin);
-       }
-       midpoint = midpoint * (1 / NumTeams(kh_teams));
-       te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5');  // make the color >=0.5 in each component
-
-       play2all(SND(KH_CAPTURE));
-       kh_FinishRound();
-}
-
-void kh_LoserTeam(int loser_team, entity lostkey)  // runs when a player pushes a flag carrier off the map
-{
-       float f;
-       entity attacker = NULL;
-       if(lostkey.pusher)
-               if(lostkey.pusher.team != loser_team)
-                       if(IS_PLAYER(lostkey.pusher))
-                               attacker = lostkey.pusher;
-
-       if(attacker)
-       {
-               if(lostkey.kh_previous_owner)
-                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
-                       // don't actually GIVE him the -nn points, just log
-               kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
-               GameRules_scoring_add(attacker, KH_PUSHES, 1);
-               //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
-       }
-       else
-       {
-               int players = 0;
-               float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-
-               FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
-
-               entity key;
-               int keys = 0;
-               FOR_EACH_KH_KEY(key)
-                       if(key.owner && key.team != loser_team)
-                               ++keys;
-
-               if(lostkey.kh_previous_owner)
-                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
-                       // don't actually GIVE him the -nn points, just log
-
-               if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)
-                       GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
-
-               DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
-
-               FOR_EACH_KH_KEY(key)
-                       if(key.owner && key.team != loser_team)
-                       {
-                               f = DistributeEvenly_Get(of);
-                               kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
-                       }
-
-               int fragsleft = DistributeEvenly_Get(players);
-
-               // Now distribute these among all other teams...
-               int j = NumTeams(kh_teams) - 1;
-               for(int i = 0; i < NumTeams(kh_teams); ++i)
-               {
-                       int thisteam = kh_Team_ByID(i);
-                       if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
-                               continue;
-
-                       players = 0;
-                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
-
-                       DistributeEvenly_Init(fragsleft, j);
-                       fragsleft = DistributeEvenly_Get(j - 1);
-                       DistributeEvenly_Init(DistributeEvenly_Get(1), players);
-
-                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
-                               f = DistributeEvenly_Get(1);
-                               kh_Scores_Event(it, NULL, "destroyed", f, 0);
-                       });
-
-                       --j;
-               }
-       }
-
-       int realteam = kh_Team_ByID(lostkey.count);
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
-       if(attacker)
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
-
-       play2all(SND(KH_DESTROY));
-       te_tarexplosion(lostkey.origin);
-
-       kh_FinishRound();
-}
-
-void kh_Key_Think(entity this)  // runs all the time
-{
-       if(game_stopped)
-               return;
-
-       if(this.owner)
-       {
-#ifndef KH_PLAYER_USE_ATTACHMENT
-               makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
-               setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
-#endif
-       }
-
-       // if in nodrop or time over, end the round
-       if(!this.owner)
-               if(time > this.pain_finished)
-                       kh_LoserTeam(this.team, this);
-
-       if(this.owner)
-       if(kh_Key_AllOwnedByWhichTeam() != -1)
-       {
-               if(this.siren_time < time)
-               {
-                       sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM);  // play a simple alarm
-                       this.siren_time = time + 2.5;  // repeat every 2.5 seconds
-               }
-
-               entity key;
-               vector p = this.owner.origin;
-               FOR_EACH_KH_KEY(key)
-                       if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
-                               goto not_winning;
-               kh_WinnerTeam(this.team);
-LABEL(not_winning)
-       }
-
-       if(kh_interferemsg_time && time > kh_interferemsg_time)
-       {
-               kh_interferemsg_time = 0;
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(it.team == kh_interferemsg_team)
-                               if(it.kh_next)
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
-                               else
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
-                       else
-                               Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
-               });
-       }
-
-       this.nextthink = time + 0.05;
-}
-
-void key_reset(entity this)
-{
-       kh_Key_AssignTo(this, NULL);
-       kh_Key_Remove(this);
-}
-
-const string STR_ITEM_KH_KEY = "item_kh_key";
-void kh_Key_Spawn(entity initial_owner, float _angle, float i)  // runs every time a new flag is created, ie after all the keys have been collected
-{
-       entity key = spawn();
-       key.count = i;
-       key.classname = STR_ITEM_KH_KEY;
-       settouch(key, kh_Key_Touch);
-       setthink(key, kh_Key_Think);
-       key.nextthink = time;
-       key.items = IT_KEY1 | IT_KEY2;
-       key.cnt = _angle;
-       key.angles = '0 360 0' * random();
-       key.event_damage = kh_Key_Damage;
-       key.takedamage = DAMAGE_YES;
-       key.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
-       key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
-       key.modelindex = kh_key_dropped;
-       key.model = "key";
-       key.kh_dropperteam = 0;
-       key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
-       setsize(key, KH_KEY_MIN, KH_KEY_MAX);
-       key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
-       key.reset = key_reset;
-       navigation_dynamicgoal_init(key, false);
-
-       switch(initial_owner.team)
-       {
-               case NUM_TEAM_1:
-                       key.netname = "^1red key";
-                       break;
-               case NUM_TEAM_2:
-                       key.netname = "^4blue key";
-                       break;
-               case NUM_TEAM_3:
-                       key.netname = "^3yellow key";
-                       break;
-               case NUM_TEAM_4:
-                       key.netname = "^6pink key";
-                       break;
-               default:
-                       key.netname = "NETGIER key";
-                       break;
-       }
-
-       // link into key list
-       key.kh_worldkeynext = kh_worldkeylist;
-       kh_worldkeylist = key;
-
-       Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START));
-
-       WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
-       key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
-
-       kh_Key_AssignTo(key, initial_owner);
-}
-
-// -1 when no team completely owns all keys yet
-int kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team
-{
-       entity key;
-       int teem = -1;
-       int keys = NumTeams(kh_teams);
-       FOR_EACH_KH_KEY(key)
-       {
-               if(!key.owner)
-                       return -1;
-               if(teem == -1)
-                       teem = key.team;
-               else if(teem != key.team)
-                       return -1;
-               --keys;
-       }
-       if(keys != 0)
-               return -1;
-       return teem;
-}
-
-void kh_Key_DropOne(entity key)
-{
-       // prevent collecting this one for some time
-       entity player = key.owner;
-
-       key.kh_droptime = time;
-       key.enemy = player;
-
-       kh_Scores_Event(player, key, "dropkey", 0, 0);
-       GameRules_scoring_add(player, KH_LOSSES, 1);
-       int realteam = kh_Team_ByID(key.count);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
-
-       kh_Key_AssignTo(key, NULL);
-       makevectors(player.v_angle);
-       key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
-       key.pusher = NULL;
-       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
-       key.kh_dropperteam = key.team;
-
-       sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
-}
-
-void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
-{
-       if(player.kh_next)
-       {
-               entity mypusher = NULL;
-               if(player.pusher)
-                       if(time < player.pushltime)
-                               mypusher = player.pusher;
-
-               entity key;
-               while((key = player.kh_next))
-               {
-                       kh_Scores_Event(player, key, "losekey", 0, 0);
-                       GameRules_scoring_add(player, KH_LOSSES, 1);
-                       int realteam = kh_Team_ByID(key.count);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
-                       kh_Key_AssignTo(key, NULL);
-                       makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
-                       key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
-                       key.pusher = mypusher;
-                       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
-                       if(suicide)
-                               key.kh_dropperteam = player.team;
-               }
-               sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
-       }
-}
-
-int kh_GetMissingTeams()
-{
-       int missing_teams = 0;
-       for(int i = 0; i < NumTeams(kh_teams); ++i)
-       {
-               int teem = kh_Team_ByID(i);
-               int players = 0;
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
-                               ++players;
-               });
-               if (!players)
-                       missing_teams |= (2 ** i);
-       }
-       return missing_teams;
-}
-
-void kh_WaitForPlayers()  // delay start of the round until enough players are present
-{
-       static int prev_missing_teams_mask;
-       if(time < game_starttime)
-       {
-               if (prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
-               return;
-       }
-
-       int missing_teams_mask = kh_GetMissingTeams();
-       if(!missing_teams_mask)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
-               kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-       }
-       else
-       {
-               if(player_count == 0)
-               {
-                       if(prev_missing_teams_mask > 0)
-                               Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-                       prev_missing_teams_mask = -1;
-               }
-               else
-               {
-                       if(prev_missing_teams_mask != missing_teams_mask)
-                       {
-                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-                               prev_missing_teams_mask = missing_teams_mask;
-                       }
-               }
-               kh_Controller_SetThink(1, kh_WaitForPlayers);
-       }
-}
-
-void kh_EnableTrackingDevice()  // runs after each round
-{
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
-       kh_tracking_enabled = true;
-}
-
-void kh_StartRound()  // runs at the start of each round
-{
-       if(time < game_starttime)
-       {
-               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
-               return;
-       }
-
-       if(kh_GetMissingTeams())
-       {
-               kh_Controller_SetThink(1, kh_WaitForPlayers);
-               return;
-       }
-
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
-       for(int i = 0; i < NumTeams(kh_teams); ++i)
-       {
-               int teem = kh_Team_ByID(i);
-               int players = 0;
-               entity my_player = NULL;
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
-                       {
-                               ++players;
-                               if(random() * players <= 1)
-                                       my_player = it;
-                       }
-               });
-               kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
-       }
-
-       kh_tracking_enabled = false;
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
-       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice);
-}
-
-float kh_HandleFrags(entity attacker, entity targ, float f)  // adds to the player score
-{
-       if(attacker == targ)
-               return f;
-
-       if(targ.kh_next)
-       {
-               if(attacker.team == targ.team)
-               {
-                       int nk = 0;
-                       for(entity k = targ.kh_next; k != NULL; k = k.kh_next)
-                               ++nk;
-                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
-               }
-               else
-               {
-                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
-                       GameRules_scoring_add(attacker, KH_KCKILLS, 1);
-                       // the frag gets added later
-               }
-       }
-
-       return f;
-}
-
-void kh_Initialize()  // sets up th KH environment
-{
-       // setup variables
-       kh_teams = autocvar_g_keyhunt_teams_override;
-       if(kh_teams < 2)
-               kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
-       kh_teams = BITS(bound(2, kh_teams, 4));
-
-       // make a KH entity for controlling the game
-       kh_controller = spawn();
-       setthink(kh_controller, kh_Controller_Think);
-       kh_Controller_SetThink(0, kh_WaitForPlayers);
-
-       setmodel(kh_controller, MDL_KH_KEY);
-       kh_key_dropped = kh_controller.modelindex;
-       /*
-       dprint(vtos(kh_controller.mins));
-       dprint(vtos(kh_controller.maxs));
-       dprint("\n");
-       */
-#ifdef KH_PLAYER_USE_CARRIEDMODEL
-       setmodel(kh_controller, MDL_KH_KEY_CARRIED);
-       kh_key_carried = kh_controller.modelindex;
-#else
-       kh_key_carried = kh_key_dropped;
-#endif
-
-       kh_controller.model = "";
-       kh_controller.modelindex = 0;
-
-       kh_ScoreRules(kh_teams);
-}
-
-void kh_finalize()
-{
-       // to be called before intermission
-       kh_FinishRound();
-       delete(kh_controller);
-       kh_controller = NULL;
-}
-
-// legacy bot role
-
-void(entity this) havocbot_role_kh_carrier;
-void(entity this) havocbot_role_kh_defense;
-void(entity this) havocbot_role_kh_offense;
-void(entity this) havocbot_role_kh_freelancer;
-
-
-void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{
-       entity head;
-       for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
-       {
-               if(head.owner == this)
-                       continue;
-               if(!kh_tracking_enabled)
-               {
-                       // if it's carried by our team we know about it
-                       // otherwise we have to see it to know about it
-                       if(!head.owner || head.team != this.team)
-                       {
-                               traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
-                               if (trace_fraction < 1 && trace_ent != head)
-                                       continue; // skip what I can't see
-                       }
-               }
-               if(!head.owner)
-                       navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
-               else if(head.team == this.team)
-                       navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
-               else
-                       navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
-       }
-
-       havocbot_goalrating_items(this, 1, this.origin, 10000);
-}
-
-void havocbot_role_kh_carrier(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (!(this.kh_next))
-       {
-               LOG_TRACE("changing role to freelancer");
-               this.havocbot_role = havocbot_role_kh_freelancer;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               if(kh_Key_AllOwnedByWhichTeam() == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
-               else
-                       havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_kh_defense(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (this.kh_next)
-       {
-               LOG_TRACE("changing role to carrier");
-               this.havocbot_role = havocbot_role_kh_carrier;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-       if (time > this.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to freelancer");
-               this.havocbot_role = havocbot_role_kh_freelancer;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               float key_owner_team;
-               navigation_goalrating_start(this);
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
-               else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_kh_offense(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (this.kh_next)
-       {
-               LOG_TRACE("changing role to carrier");
-               this.havocbot_role = havocbot_role_kh_carrier;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-       if (time > this.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to freelancer");
-               this.havocbot_role = havocbot_role_kh_freelancer;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               float key_owner_team;
-
-               navigation_goalrating_start(this);
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
-               else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_kh_freelancer(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (this.kh_next)
-       {
-               LOG_TRACE("changing role to carrier");
-               this.havocbot_role = havocbot_role_kh_carrier;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 10;
-       if (time > this.havocbot_role_timeout)
-       {
-               if (random() < 0.5)
-               {
-                       LOG_TRACE("changing role to offense");
-                       this.havocbot_role = havocbot_role_kh_offense;
-               }
-               else
-               {
-                       LOG_TRACE("changing role to defense");
-                       this.havocbot_role = havocbot_role_kh_defense;
-               }
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               int key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
-               else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-
-// register this as a mutator
-
-MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-
-       if(frag_target == frag_attacker)
-               kh_Key_DropAll(frag_target, true);
-       else if(IS_PLAYER(frag_attacker))
-               kh_Key_DropAll(frag_target, false);
-       else
-               kh_Key_DropAll(frag_target, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       entity frag_attacker = M_ARGV(0, entity);
-       entity frag_target = M_ARGV(1, entity);
-       float frag_score = M_ARGV(2, float);
-       M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MatchEnd)
-{
-       kh_finalize();
-}
-
-MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = kh_teams;
-}
-
-MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
-{
-       entity spectatee = M_ARGV(0, entity);
-       entity client = M_ARGV(1, entity);
-
-       STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(MUTATOR_RETURNVALUE == 0)
-       {
-               entity k = player.kh_next;
-               if(k)
-               {
-                       kh_Key_DropOne(k);
-                       return true;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
-{
-    entity bot = M_ARGV(0, entity);
-
-       if(IS_DEAD(bot))
-               return true;
-
-       float r = random() * 3;
-       if (r < 1)
-               bot.havocbot_role = havocbot_role_kh_offense;
-       else if (r < 2)
-               bot.havocbot_role = havocbot_role_kh_defense;
-       else
-               bot.havocbot_role = havocbot_role_kh_freelancer;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
-{
-       entity frag_target = M_ARGV(0, entity);
-
-       kh_Key_DropAll(frag_target, false);
-}
-
-MUTATOR_HOOKFUNCTION(kh, reset_map_global)
-{
-       kh_WaitForPlayers(); // takes care of killing the "missing teams" message
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh
deleted file mode 100644 (file)
index a086ee6..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
-int autocvar_g_keyhunt_point_leadlimit;
-bool autocvar_g_keyhunt_team_spawns;
-void kh_Initialize();
-
-REGISTER_MUTATOR(kh, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
-        GameRules_limit_score(autocvar_g_keyhunt_point_limit);
-        GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
-
-               kh_Initialize();
-       }
-       return 0;
-}
-
-#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )
-
-// ALL OF THESE should be removed in the future, as other code should not have to care
-
-// used by bots:
-bool kh_tracking_enabled;
-.entity kh_next;
-
-USING(kh_Think_t, void());
-void kh_StartRound();
-void kh_Controller_SetThink(float t, kh_Think_t func);
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc
new file mode 100644 (file)
index 0000000..f845673
--- /dev/null
@@ -0,0 +1,1304 @@
+#include "sv_keyhunt.qh"
+
+float autocvar_g_balance_keyhunt_damageforcescale;
+float autocvar_g_balance_keyhunt_delay_collect;
+float autocvar_g_balance_keyhunt_delay_damage_return;
+float autocvar_g_balance_keyhunt_delay_return;
+float autocvar_g_balance_keyhunt_delay_round;
+float autocvar_g_balance_keyhunt_delay_tracking;
+float autocvar_g_balance_keyhunt_return_when_unreachable;
+float autocvar_g_balance_keyhunt_dropvelocity;
+float autocvar_g_balance_keyhunt_maxdist;
+float autocvar_g_balance_keyhunt_protecttime;
+
+int autocvar_g_balance_keyhunt_score_capture;
+int autocvar_g_balance_keyhunt_score_carrierfrag;
+int autocvar_g_balance_keyhunt_score_collect;
+int autocvar_g_balance_keyhunt_score_destroyed;
+int autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+int autocvar_g_balance_keyhunt_score_push;
+float autocvar_g_balance_keyhunt_throwvelocity;
+
+//int autocvar_g_keyhunt_teams;
+int autocvar_g_keyhunt_teams_override;
+
+// #define KH_PLAYER_USE_ATTACHMENT
+// #define KH_PLAYER_USE_CARRIEDMODEL
+
+#ifdef KH_PLAYER_USE_ATTACHMENT
+const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
+const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
+const vector KH_PLAYER_ATTACHMENT = '0 0 0';
+const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
+const string KH_PLAYER_ATTACHMENT_BONE = "";
+#else
+const float KH_KEY_ZSHIFT = 22;
+const float KH_KEY_XYDIST = 24;
+const float KH_KEY_XYSPEED = 45;
+#endif
+const float KH_KEY_WP_ZSHIFT = 20;
+
+const vector KH_KEY_MIN = '-10 -10 -46';
+const vector KH_KEY_MAX = '10 10 3';
+const float KH_KEY_BRIGHTNESS = 2;
+
+bool kh_no_radar_circles;
+
+// kh_state
+//     bits  0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
+//     bits  5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
+//     bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
+//     bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
+.float siren_time;  //  time delay the siren
+//.float stuff_time;  //  time delay to stuffcmd a cvar
+
+int kh_Team_ByID(int t)
+{
+       if(t == 0) return NUM_TEAM_1;
+       if(t == 1) return NUM_TEAM_2;
+       if(t == 2) return NUM_TEAM_3;
+       if(t == 3) return NUM_TEAM_4;
+       return 0;
+}
+
+//entity kh_worldkeylist;
+.entity kh_worldkeynext;
+entity kh_controller;
+//bool kh_tracking_enabled;
+int kh_teams;
+int kh_interferemsg_team;
+float kh_interferemsg_time;
+.entity kh_next, kh_prev; // linked list
+.float kh_droptime;
+.int kh_dropperteam;
+.entity kh_previous_owner;
+.int kh_previous_owner_playerid;
+
+int kh_key_dropped, kh_key_carried;
+
+int kh_Key_AllOwnedByWhichTeam();
+
+const int ST_KH_CAPS = 1;
+void kh_ScoreRules(int teams)
+{
+       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+        field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+        field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+        field(SP_KH_PUSHES, "pushes", 0);
+        field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
+        field(SP_KH_PICKUPS, "pickups", 0);
+        field(SP_KH_KCKILLS, "kckills", 0);
+        field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
+       });
+}
+
+bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view)  // runs all the time
+{
+       if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
+               if(!kh_tracking_enabled)
+                       return false;
+
+       return true;
+}
+
+bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
+{
+       if(!kh_tracking_enabled)
+               return false;
+       if(!this.owner)
+               return true;
+       if(!this.owner.owner)
+               return true;
+       return false;  // draw only when key is not owned
+}
+
+void kh_update_state()
+{
+       entity key;
+       int f;
+       int s = 0;
+       FOR_EACH_KH_KEY(key)
+       {
+               if(key.owner)
+                       f = key.team;
+               else
+                       f = 30;
+               s |= (32 ** key.count) * f;
+       }
+
+       FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
+
+       FOR_EACH_KH_KEY(key)
+       {
+               if(key.owner)
+                       STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
+       }
+       //print(ftos((nextent(NULL)).kh_state), "\n");
+}
+
+
+
+
+var kh_Think_t kh_Controller_Thinkfunc;
+void kh_Controller_SetThink(float t, kh_Think_t func)  // runs occasionaly
+{
+       kh_Controller_Thinkfunc = func;
+       kh_controller.cnt = ceil(t);
+       if(t == 0)
+               kh_controller.nextthink = time; // force
+}
+void kh_WaitForPlayers();
+void kh_Controller_Think(entity this)  // called a lot
+{
+       if(game_stopped)
+               return;
+       if(this.cnt > 0)
+       {
+               if(getthink(this) != kh_WaitForPlayers)
+                       this.cnt -= 1;
+       }
+       else if(this.cnt == 0)
+       {
+               this.cnt -= 1;
+               kh_Controller_Thinkfunc();
+       }
+       this.nextthink = time + 1;
+}
+
+// frags f: take from cvar * f
+// frags 0: no frags
+void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)  // update the score when a key is captured
+{
+       string s;
+       if(game_stopped)
+               return;
+
+       if(frags_player)
+               UpdateFrags(player, frags_player);
+
+       if(key && key.owner && frags_owner)
+               UpdateFrags(key.owner, frags_owner);
+
+       if(!autocvar_sv_eventlog)  //output extra info to the console or text file
+               return;
+
+       s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
+
+       if(key && key.owner)
+               s = strcat(s, ":", ftos(key.owner.playerid));
+       else
+               s = strcat(s, ":0");
+
+       s = strcat(s, ":", ftos(frags_owner), ":");
+
+       if(key)
+               s = strcat(s, key.netname);
+
+       GameLogEcho(s);
+}
+
+vector kh_AttachedOrigin(entity e)  // runs when a team captures the flag, it can run 2 or 3 times.
+{
+       if(e.tag_entity)
+       {
+               makevectors(e.tag_entity.angles);
+               return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
+       }
+       else
+               return e.origin;
+}
+
+void kh_Key_Attach(entity key)  // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
+{
+#ifdef KH_PLAYER_USE_ATTACHMENT
+       entity first = key.owner.kh_next;
+       if(key == first)
+       {
+               setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
+               if(key.kh_next)
+               {
+                       setattachment(key.kh_next, key, "");
+                       setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+                       setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
+                       key.kh_next.angles = '0 0 0';
+               }
+               else
+                       setorigin(key, KH_PLAYER_ATTACHMENT);
+               key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
+       }
+       else
+       {
+               setattachment(key, key.kh_prev, "");
+               if(key.kh_next)
+                       setattachment(key.kh_next, key, "");
+               setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
+               setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+               key.angles = '0 0 0';
+       }
+#else
+       setattachment(key, key.owner, "");
+       setorigin(key, '0 0 1' * KH_KEY_ZSHIFT);  // fixing x, y in think
+       key.angles_y -= key.owner.angles.y;
+#endif
+       key.flags = 0;
+       if(IL_CONTAINS(g_items, key))
+               IL_REMOVE(g_items, key);
+       key.solid = SOLID_NOT;
+       set_movetype(key, MOVETYPE_NONE);
+       key.team = key.owner.team;
+       key.nextthink = time;
+       key.damageforcescale = 0;
+       key.takedamage = DAMAGE_NO;
+       key.modelindex = kh_key_carried;
+       navigation_dynamicgoal_unset(key);
+}
+
+void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
+{
+#ifdef KH_PLAYER_USE_ATTACHMENT
+       entity first = key.owner.kh_next;
+       if(key == first)
+       {
+               if(key.kh_next)
+               {
+                       setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
+                       setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+                       key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
+               }
+       }
+       else
+       {
+               if(key.kh_next)
+                       setattachment(key.kh_next, key.kh_prev, "");
+               setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+       }
+       // in any case:
+       setattachment(key, NULL, "");
+       setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
+       key.angles = key.owner.angles;
+#else
+       setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
+       setattachment(key, NULL, "");
+       key.angles_y += key.owner.angles.y;
+#endif
+       key.flags = FL_ITEM;
+       if(!IL_CONTAINS(g_items, key))
+               IL_PUSH(g_items, key);
+       key.solid = SOLID_TRIGGER;
+       set_movetype(key, MOVETYPE_TOSS);
+       key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
+       key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
+       key.takedamage = DAMAGE_YES;
+       // let key.team stay
+       key.modelindex = kh_key_dropped;
+       navigation_dynamicgoal_set(key, key.owner);
+       key.kh_previous_owner = key.owner;
+       key.kh_previous_owner_playerid = key.owner.playerid;
+}
+
+void kh_Key_AssignTo(entity key, entity player)  // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
+{
+       if(key.owner == player)
+               return;
+
+       int ownerteam0 = kh_Key_AllOwnedByWhichTeam();
+
+       if(key.owner)
+       {
+               kh_Key_Detach(key);
+
+               // remove from linked list
+               if(key.kh_next)
+                       key.kh_next.kh_prev = key.kh_prev;
+               key.kh_prev.kh_next = key.kh_next;
+               key.kh_next = NULL;
+               key.kh_prev = NULL;
+
+               if(key.owner.kh_next == NULL)
+               {
+                       // No longer a key carrier
+                       if(!kh_no_radar_circles)
+                               WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
+                       WaypointSprite_DetachCarrier(key.owner);
+               }
+       }
+
+       key.owner = player;
+
+       if(player)
+       {
+               // insert into linked list
+               key.kh_next = player.kh_next;
+               key.kh_prev = player;
+               player.kh_next = key;
+               if(key.kh_next)
+                       key.kh_next.kh_prev = key;
+
+               kh_Key_Attach(key);
+
+               if(key.kh_next == NULL)
+               {
+                       // player is now a key carrier
+                       entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
+                       wp.colormod = colormapPaletteColor(player.team - 1, 0);
+                       player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
+                       WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
+                       if(player.team == NUM_TEAM_1)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
+                       else if(player.team == NUM_TEAM_2)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
+                       else if(player.team == NUM_TEAM_3)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
+                       else if(player.team == NUM_TEAM_4)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
+                       if(!kh_no_radar_circles)
+                               WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
+               }
+       }
+
+       // moved that here, also update if there's no player
+       kh_update_state();
+
+       key.pusher = NULL;
+
+       int ownerteam = kh_Key_AllOwnedByWhichTeam();
+       if(ownerteam != ownerteam0)
+       {
+               entity k;
+               if(ownerteam != -1)
+               {
+                       kh_interferemsg_time = time + 0.2;
+                       kh_interferemsg_team = player.team;
+
+                       // audit all key carrier sprites, update them to "Run here"
+                       FOR_EACH_KH_KEY(k)
+                       {
+                               if (!k.owner) continue;
+                               entity first = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
+                               entity third = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
+                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
+                       }
+               }
+               else
+               {
+                       kh_interferemsg_time = 0;
+
+                       // audit all key carrier sprites, update them to "Key Carrier"
+                       FOR_EACH_KH_KEY(k)
+                       {
+                               if (!k.owner) continue;
+                               entity first = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
+                               entity third = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
+                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
+                       }
+               }
+       }
+}
+
+void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+       if(this.owner)
+               return;
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+               return;
+       }
+       if(force == '0 0 0')
+               return;
+       if(time > this.pushltime)
+               if(IS_PLAYER(attacker))
+                       this.team = attacker.team;
+}
+
+void kh_Key_Collect(entity key, entity player)  //a player picks up a dropped key
+{
+       sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
+
+       if(key.kh_dropperteam != player.team)
+       {
+               kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0);
+               GameRules_scoring_add(player, KH_PICKUPS, 1);
+       }
+       key.kh_dropperteam = 0;
+       int realteam = kh_Team_ByID(key.count);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
+
+       kh_Key_AssignTo(key, player); // this also updates .kh_state
+}
+
+void kh_Key_Touch(entity this, entity toucher)  // runs many, many times when a key has been dropped and can be picked up
+{
+       if(game_stopped)
+               return;
+
+       if(this.owner) // already carried
+               return;
+
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+               return;
+       }
+
+       if (!IS_PLAYER(toucher))
+               return;
+       if(IS_DEAD(toucher))
+               return;
+       if(toucher == this.enemy)
+               if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
+                       return;  // you just dropped it!
+       kh_Key_Collect(this, toucher);
+}
+
+void kh_Key_Remove(entity key)  // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
+{
+       entity o = key.owner;
+       kh_Key_AssignTo(key, NULL);
+       if(o) // it was attached
+               WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
+       else // it was dropped
+               WaypointSprite_DetachCarrier(key);
+
+       // remove key from key list
+       if (kh_worldkeylist == key)
+               kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
+       else
+       {
+               o = kh_worldkeylist;
+               while (o)
+               {
+                       if (o.kh_worldkeynext == key)
+                       {
+                               o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
+                               break;
+                       }
+                       o = o.kh_worldkeynext;
+               }
+       }
+
+       delete(key);
+
+       kh_update_state();
+}
+
+void kh_FinishRound()  // runs when a team captures the keys
+{
+       // prepare next round
+       kh_interferemsg_time = 0;
+       entity key;
+
+       kh_no_radar_circles = true;
+       FOR_EACH_KH_KEY(key)
+               kh_Key_Remove(key);
+       kh_no_radar_circles = false;
+
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+}
+
+void nades_GiveBonus(entity player, float score);
+
+void kh_WinnerTeam(int winner_team)  // runs when a team wins
+{
+       // all key carriers get some points
+       entity key;
+       float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
+       DistributeEvenly_Init(score, NumTeams(kh_teams));
+       // twice the score for 3 team games, three times the score for 4 team games!
+       // note: for a win by destroying the key, this should NOT be applied
+       FOR_EACH_KH_KEY(key)
+       {
+               float f = DistributeEvenly_Get(1);
+               kh_Scores_Event(key.owner, key, "capture", f, 0);
+               GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
+               nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
+       }
+
+       bool first = true;
+       string keyowner = "";
+       FOR_EACH_KH_KEY(key)
+               if(key.owner.kh_next == key)
+               {
+                       if(!first)
+                               keyowner = strcat(keyowner, ", ");
+                       keyowner = key.owner.netname;
+                       first = false;
+               }
+
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
+
+       first = true;
+       vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
+       FOR_EACH_KH_KEY(key)
+       {
+               vector thisorigin = kh_AttachedOrigin(key);
+               //dprint("Key origin: ", vtos(thisorigin), "\n");
+               midpoint += thisorigin;
+
+               if(!first)
+                       te_lightning2(NULL, lastorigin, thisorigin);
+               lastorigin = thisorigin;
+               if(first)
+                       firstorigin = thisorigin;
+               first = false;
+       }
+       if(NumTeams(kh_teams) > 2)
+       {
+               te_lightning2(NULL, lastorigin, firstorigin);
+       }
+       midpoint = midpoint * (1 / NumTeams(kh_teams));
+       te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5');  // make the color >=0.5 in each component
+
+       play2all(SND(KH_CAPTURE));
+       kh_FinishRound();
+}
+
+void kh_LoserTeam(int loser_team, entity lostkey)  // runs when a player pushes a flag carrier off the map
+{
+       float f;
+       entity attacker = NULL;
+       if(lostkey.pusher)
+               if(lostkey.pusher.team != loser_team)
+                       if(IS_PLAYER(lostkey.pusher))
+                               attacker = lostkey.pusher;
+
+       if(attacker)
+       {
+               if(lostkey.kh_previous_owner)
+                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
+                       // don't actually GIVE him the -nn points, just log
+               kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
+               GameRules_scoring_add(attacker, KH_PUSHES, 1);
+               //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
+       }
+       else
+       {
+               int players = 0;
+               float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+
+               FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
+
+               entity key;
+               int keys = 0;
+               FOR_EACH_KH_KEY(key)
+                       if(key.owner && key.team != loser_team)
+                               ++keys;
+
+               if(lostkey.kh_previous_owner)
+                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
+                       // don't actually GIVE him the -nn points, just log
+
+               if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)
+                       GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
+
+               DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
+
+               FOR_EACH_KH_KEY(key)
+                       if(key.owner && key.team != loser_team)
+                       {
+                               f = DistributeEvenly_Get(of);
+                               kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
+                       }
+
+               int fragsleft = DistributeEvenly_Get(players);
+
+               // Now distribute these among all other teams...
+               int j = NumTeams(kh_teams) - 1;
+               for(int i = 0; i < NumTeams(kh_teams); ++i)
+               {
+                       int thisteam = kh_Team_ByID(i);
+                       if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
+                               continue;
+
+                       players = 0;
+                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
+
+                       DistributeEvenly_Init(fragsleft, j);
+                       fragsleft = DistributeEvenly_Get(j - 1);
+                       DistributeEvenly_Init(DistributeEvenly_Get(1), players);
+
+                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
+                               f = DistributeEvenly_Get(1);
+                               kh_Scores_Event(it, NULL, "destroyed", f, 0);
+                       });
+
+                       --j;
+               }
+       }
+
+       int realteam = kh_Team_ByID(lostkey.count);
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
+       if(attacker)
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
+       else
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
+
+       play2all(SND(KH_DESTROY));
+       te_tarexplosion(lostkey.origin);
+
+       kh_FinishRound();
+}
+
+void kh_Key_Think(entity this)  // runs all the time
+{
+       if(game_stopped)
+               return;
+
+       if(this.owner)
+       {
+#ifndef KH_PLAYER_USE_ATTACHMENT
+               makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
+               setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
+#endif
+       }
+
+       // if in nodrop or time over, end the round
+       if(!this.owner)
+               if(time > this.pain_finished)
+                       kh_LoserTeam(this.team, this);
+
+       if(this.owner)
+       if(kh_Key_AllOwnedByWhichTeam() != -1)
+       {
+               if(this.siren_time < time)
+               {
+                       sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM);  // play a simple alarm
+                       this.siren_time = time + 2.5;  // repeat every 2.5 seconds
+               }
+
+               entity key;
+               vector p = this.owner.origin;
+               FOR_EACH_KH_KEY(key)
+                       if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
+                               goto not_winning;
+               kh_WinnerTeam(this.team);
+LABEL(not_winning)
+       }
+
+       if(kh_interferemsg_time && time > kh_interferemsg_time)
+       {
+               kh_interferemsg_time = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(it.team == kh_interferemsg_team)
+                               if(it.kh_next)
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
+                               else
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
+                       else
+                               Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
+               });
+       }
+
+       this.nextthink = time + 0.05;
+}
+
+void key_reset(entity this)
+{
+       kh_Key_AssignTo(this, NULL);
+       kh_Key_Remove(this);
+}
+
+const string STR_ITEM_KH_KEY = "item_kh_key";
+void kh_Key_Spawn(entity initial_owner, float _angle, float i)  // runs every time a new flag is created, ie after all the keys have been collected
+{
+       entity key = spawn();
+       key.count = i;
+       key.classname = STR_ITEM_KH_KEY;
+       settouch(key, kh_Key_Touch);
+       setthink(key, kh_Key_Think);
+       key.nextthink = time;
+       key.items = IT_KEY1 | IT_KEY2;
+       key.cnt = _angle;
+       key.angles = '0 360 0' * random();
+       key.event_damage = kh_Key_Damage;
+       key.takedamage = DAMAGE_YES;
+       key.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
+       key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
+       key.modelindex = kh_key_dropped;
+       key.model = "key";
+       key.kh_dropperteam = 0;
+       key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+       setsize(key, KH_KEY_MIN, KH_KEY_MAX);
+       key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
+       key.reset = key_reset;
+       navigation_dynamicgoal_init(key, false);
+
+       switch(initial_owner.team)
+       {
+               case NUM_TEAM_1:
+                       key.netname = "^1red key";
+                       break;
+               case NUM_TEAM_2:
+                       key.netname = "^4blue key";
+                       break;
+               case NUM_TEAM_3:
+                       key.netname = "^3yellow key";
+                       break;
+               case NUM_TEAM_4:
+                       key.netname = "^6pink key";
+                       break;
+               default:
+                       key.netname = "NETGIER key";
+                       break;
+       }
+
+       // link into key list
+       key.kh_worldkeynext = kh_worldkeylist;
+       kh_worldkeylist = key;
+
+       Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START));
+
+       WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
+       key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
+
+       kh_Key_AssignTo(key, initial_owner);
+}
+
+// -1 when no team completely owns all keys yet
+int kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team
+{
+       entity key;
+       int teem = -1;
+       int keys = NumTeams(kh_teams);
+       FOR_EACH_KH_KEY(key)
+       {
+               if(!key.owner)
+                       return -1;
+               if(teem == -1)
+                       teem = key.team;
+               else if(teem != key.team)
+                       return -1;
+               --keys;
+       }
+       if(keys != 0)
+               return -1;
+       return teem;
+}
+
+void kh_Key_DropOne(entity key)
+{
+       // prevent collecting this one for some time
+       entity player = key.owner;
+
+       key.kh_droptime = time;
+       key.enemy = player;
+
+       kh_Scores_Event(player, key, "dropkey", 0, 0);
+       GameRules_scoring_add(player, KH_LOSSES, 1);
+       int realteam = kh_Team_ByID(key.count);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
+
+       kh_Key_AssignTo(key, NULL);
+       makevectors(player.v_angle);
+       key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
+       key.pusher = NULL;
+       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
+       key.kh_dropperteam = key.team;
+
+       sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
+}
+
+void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
+{
+       if(player.kh_next)
+       {
+               entity mypusher = NULL;
+               if(player.pusher)
+                       if(time < player.pushltime)
+                               mypusher = player.pusher;
+
+               entity key;
+               while((key = player.kh_next))
+               {
+                       kh_Scores_Event(player, key, "losekey", 0, 0);
+                       GameRules_scoring_add(player, KH_LOSSES, 1);
+                       int realteam = kh_Team_ByID(key.count);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
+                       kh_Key_AssignTo(key, NULL);
+                       makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
+                       key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
+                       key.pusher = mypusher;
+                       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
+                       if(suicide)
+                               key.kh_dropperteam = player.team;
+               }
+               sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
+       }
+}
+
+int kh_GetMissingTeams()
+{
+       int missing_teams = 0;
+       for(int i = 0; i < NumTeams(kh_teams); ++i)
+       {
+               int teem = kh_Team_ByID(i);
+               int players = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+                               ++players;
+               });
+               if (!players)
+                       missing_teams |= (2 ** i);
+       }
+       return missing_teams;
+}
+
+void kh_WaitForPlayers()  // delay start of the round until enough players are present
+{
+       static int prev_missing_teams_mask;
+       if(time < game_starttime)
+       {
+               if (prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+               return;
+       }
+
+       int missing_teams_mask = kh_GetMissingTeams();
+       if(!missing_teams_mask)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+               kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+       }
+       else
+       {
+               if(player_count == 0)
+               {
+                       if(prev_missing_teams_mask > 0)
+                               Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+                       prev_missing_teams_mask = -1;
+               }
+               else
+               {
+                       if(prev_missing_teams_mask != missing_teams_mask)
+                       {
+                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+                               prev_missing_teams_mask = missing_teams_mask;
+                       }
+               }
+               kh_Controller_SetThink(1, kh_WaitForPlayers);
+       }
+}
+
+void kh_EnableTrackingDevice()  // runs after each round
+{
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+       kh_tracking_enabled = true;
+}
+
+void kh_StartRound()  // runs at the start of each round
+{
+       if(time < game_starttime)
+       {
+               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+               return;
+       }
+
+       if(kh_GetMissingTeams())
+       {
+               kh_Controller_SetThink(1, kh_WaitForPlayers);
+               return;
+       }
+
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+       for(int i = 0; i < NumTeams(kh_teams); ++i)
+       {
+               int teem = kh_Team_ByID(i);
+               int players = 0;
+               entity my_player = NULL;
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+                       {
+                               ++players;
+                               if(random() * players <= 1)
+                                       my_player = it;
+                       }
+               });
+               kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
+       }
+
+       kh_tracking_enabled = false;
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
+       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice);
+}
+
+float kh_HandleFrags(entity attacker, entity targ, float f)  // adds to the player score
+{
+       if(attacker == targ)
+               return f;
+
+       if(targ.kh_next)
+       {
+               if(attacker.team == targ.team)
+               {
+                       int nk = 0;
+                       for(entity k = targ.kh_next; k != NULL; k = k.kh_next)
+                               ++nk;
+                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
+               }
+               else
+               {
+                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
+                       GameRules_scoring_add(attacker, KH_KCKILLS, 1);
+                       // the frag gets added later
+               }
+       }
+
+       return f;
+}
+
+void kh_Initialize()  // sets up th KH environment
+{
+       // setup variables
+       kh_teams = autocvar_g_keyhunt_teams_override;
+       if(kh_teams < 2)
+               kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
+       kh_teams = BITS(bound(2, kh_teams, 4));
+
+       // make a KH entity for controlling the game
+       kh_controller = spawn();
+       setthink(kh_controller, kh_Controller_Think);
+       kh_Controller_SetThink(0, kh_WaitForPlayers);
+
+       setmodel(kh_controller, MDL_KH_KEY);
+       kh_key_dropped = kh_controller.modelindex;
+       /*
+       dprint(vtos(kh_controller.mins));
+       dprint(vtos(kh_controller.maxs));
+       dprint("\n");
+       */
+#ifdef KH_PLAYER_USE_CARRIEDMODEL
+       setmodel(kh_controller, MDL_KH_KEY_CARRIED);
+       kh_key_carried = kh_controller.modelindex;
+#else
+       kh_key_carried = kh_key_dropped;
+#endif
+
+       kh_controller.model = "";
+       kh_controller.modelindex = 0;
+
+       kh_ScoreRules(kh_teams);
+}
+
+void kh_finalize()
+{
+       // to be called before intermission
+       kh_FinishRound();
+       delete(kh_controller);
+       kh_controller = NULL;
+}
+
+// legacy bot role
+
+void(entity this) havocbot_role_kh_carrier;
+void(entity this) havocbot_role_kh_defense;
+void(entity this) havocbot_role_kh_offense;
+void(entity this) havocbot_role_kh_freelancer;
+
+
+void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{
+       entity head;
+       for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
+       {
+               if(head.owner == this)
+                       continue;
+               if(!kh_tracking_enabled)
+               {
+                       // if it's carried by our team we know about it
+                       // otherwise we have to see it to know about it
+                       if(!head.owner || head.team != this.team)
+                       {
+                               traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
+                               if (trace_fraction < 1 && trace_ent != head)
+                                       continue; // skip what I can't see
+                       }
+               }
+               if(!head.owner)
+                       navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
+               else if(head.team == this.team)
+                       navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
+               else
+                       navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
+       }
+
+       havocbot_goalrating_items(this, 1, this.origin, 10000);
+}
+
+void havocbot_role_kh_carrier(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (!(this.kh_next))
+       {
+               LOG_TRACE("changing role to freelancer");
+               this.havocbot_role = havocbot_role_kh_freelancer;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               if(kh_Key_AllOwnedByWhichTeam() == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // bring home
+               else
+                       havocbot_goalrating_kh(this, 4, 4, 0.5); // play defensively
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_kh_defense(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (this.kh_next)
+       {
+               LOG_TRACE("changing role to carrier");
+               this.havocbot_role = havocbot_role_kh_carrier;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+       if (time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freelancer");
+               this.havocbot_role = havocbot_role_kh_freelancer;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               float key_owner_team;
+               navigation_goalrating_start(this);
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend key carriers
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(this, 4, 1, 0.05); // play defensively
+               else
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_kh_offense(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (this.kh_next)
+       {
+               LOG_TRACE("changing role to carrier");
+               this.havocbot_role = havocbot_role_kh_carrier;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+       if (time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freelancer");
+               this.havocbot_role = havocbot_role_kh_freelancer;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               float key_owner_team;
+
+               navigation_goalrating_start(this);
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(this, 0.1, 1, 2); // play offensively
+               else
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK! EMERGENCY!
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_kh_freelancer(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (this.kh_next)
+       {
+               LOG_TRACE("changing role to carrier");
+               this.havocbot_role = havocbot_role_kh_carrier;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 10;
+       if (time > this.havocbot_role_timeout)
+       {
+               if (random() < 0.5)
+               {
+                       LOG_TRACE("changing role to offense");
+                       this.havocbot_role = havocbot_role_kh_offense;
+               }
+               else
+               {
+                       LOG_TRACE("changing role to defense");
+                       this.havocbot_role = havocbot_role_kh_defense;
+               }
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               int key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(this, 1, 10, 2); // prefer dropped keys
+               else
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+
+// register this as a mutator
+
+MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+
+       if(frag_target == frag_attacker)
+               kh_Key_DropAll(frag_target, true);
+       else if(IS_PLAYER(frag_attacker))
+               kh_Key_DropAll(frag_target, false);
+       else
+               kh_Key_DropAll(frag_target, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       entity frag_attacker = M_ARGV(0, entity);
+       entity frag_target = M_ARGV(1, entity);
+       float frag_score = M_ARGV(2, float);
+       M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MatchEnd)
+{
+       kh_finalize();
+}
+
+MUTATOR_HOOKFUNCTION(kh, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = kh_teams;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
+{
+       entity spectatee = M_ARGV(0, entity);
+       entity client = M_ARGV(1, entity);
+
+       STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(MUTATOR_RETURNVALUE == 0)
+       {
+               entity k = player.kh_next;
+               if(k)
+               {
+                       kh_Key_DropOne(k);
+                       return true;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
+{
+    entity bot = M_ARGV(0, entity);
+
+       if(IS_DEAD(bot))
+               return true;
+
+       float r = random() * 3;
+       if (r < 1)
+               bot.havocbot_role = havocbot_role_kh_offense;
+       else if (r < 2)
+               bot.havocbot_role = havocbot_role_kh_defense;
+       else
+               bot.havocbot_role = havocbot_role_kh_freelancer;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
+{
+       entity frag_target = M_ARGV(0, entity);
+
+       kh_Key_DropAll(frag_target, false);
+}
+
+MUTATOR_HOOKFUNCTION(kh, reset_map_global)
+{
+       kh_WaitForPlayers(); // takes care of killing the "missing teams" message
+}
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qh
new file mode 100644 (file)
index 0000000..345a3d1
--- /dev/null
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
+int autocvar_g_keyhunt_point_leadlimit;
+bool autocvar_g_keyhunt_team_spawns;
+void kh_Initialize();
+
+REGISTER_MUTATOR(kh, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
+        GameRules_limit_score(autocvar_g_keyhunt_point_limit);
+        GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
+
+               kh_Initialize();
+       }
+       return 0;
+}
+
+#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )
+
+// ALL OF THESE should be removed in the future, as other code should not have to care
+
+// used by bots:
+bool kh_tracking_enabled;
+.entity kh_next;
+
+USING(kh_Think_t, void());
+void kh_StartRound();
+void kh_Controller_SetThink(float t, kh_Think_t func);
index 43bb76d7faa792950ad34bc54a4eb3d41d00d788..fcf63d7cc9eb7b8ec640083f63c8e39fdc92820c 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/lms/lms.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/lms/sv_lms.qc>
+#endif
index 5e780bb674cc02a35659ca0154060166626f8085..51c1ee15f4d571cbb551c755ce6049c585f9d583 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/lms/lms.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/lms/sv_lms.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/lms/lms.qc b/qcsrc/common/gamemodes/gamemode/lms/lms.qc
deleted file mode 100644 (file)
index b6bfb4e..0000000
+++ /dev/null
@@ -1,438 +0,0 @@
-#include "lms.qh"
-
-#ifdef SVQC
-#include <common/mutators/mutator/instagib/items.qh>
-#include <server/campaign.qh>
-#include <server/command/_mod.qh>
-
-int autocvar_g_lms_extra_lives;
-bool autocvar_g_lms_join_anytime;
-int autocvar_g_lms_last_join;
-bool autocvar_g_lms_regenerate;
-
-// main functions
-float LMS_NewPlayerLives()
-{
-       float fl;
-       fl = autocvar_fraglimit;
-       if(fl == 0)
-               fl = 999;
-
-       // first player has left the game for dying too much? Nobody else can get in.
-       if(lms_lowest_lives < 1)
-               return 0;
-
-       if(!autocvar_g_lms_join_anytime)
-               if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
-                       return 0;
-
-       return bound(1, lms_lowest_lives, fl);
-}
-
-void ClearWinners();
-
-// LMS winning condition: game terminates if and only if there's at most one
-// one player who's living lives. Top two scores being equal cancels the time
-// limit.
-int WinningCondition_LMS()
-{
-       entity first_player = NULL;
-       int total_players = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               if (!total_players)
-                       first_player = it;
-               ++total_players;
-       });
-
-       if (total_players)
-       {
-               if (total_players > 1)
-               {
-                       // two or more active players - continue with the game
-
-                       if (autocvar_g_campaign)
-                       {
-                               FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-                                       float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
-                                       if (!pl_lives)
-                                               return WINNING_YES; // human player lost, game over
-                                       break;
-                               });
-                       }
-               }
-               else
-               {
-                       // exactly one player?
-
-                       ClearWinners();
-                       SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
-
-                       if (LMS_NewPlayerLives())
-                       {
-                               // game still running (that is, nobody got removed from the game by a frag yet)? then continue
-                               return WINNING_NO;
-                       }
-                       else
-                       {
-                               // a winner!
-                               // and assign him his first place
-                               GameRules_scoring_add(first_player, LMS_RANK, 1);
-                               if(warmup_stage)
-                                       return WINNING_NO;
-                               else
-                                       return WINNING_YES;
-                       }
-               }
-       }
-       else
-       {
-               // nobody is playing at all...
-               if (LMS_NewPlayerLives())
-               {
-                       // wait for players...
-               }
-               else
-               {
-                       // SNAFU (maybe a draw game?)
-                       ClearWinners();
-                       LOG_TRACE("No players, ending game.");
-                       return WINNING_YES;
-               }
-       }
-
-       // When we get here, we have at least two players who are actually LIVING,
-       // now check if the top two players have equal score.
-       WinningConditionHelper(NULL);
-
-       ClearWinners();
-       if(WinningConditionHelper_winner)
-               WinningConditionHelper_winner.winning = true;
-       if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
-               return WINNING_NEVER;
-
-       // Top two have different scores? Way to go for our beloved TIMELIMIT!
-       return WINNING_NO;
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(lms, reset_map_global)
-{
-       lms_lowest_lives = 999;
-}
-
-MUTATOR_HOOKFUNCTION(lms, reset_map_players)
-{
-       FOREACH_CLIENT(true, {
-               TRANSMUTE(Player, it);
-               it.frags = FRAGS_PLAYER;
-               GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
-               PutClientInServer(it);
-       });
-}
-
-MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.frags == FRAGS_SPECTATOR)
-               TRANSMUTE(Observer, player);
-       else
-       {
-               float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
-               if(tl < lms_lowest_lives)
-                       lms_lowest_lives = tl;
-               if(tl <= 0)
-                       TRANSMUTE(Observer, player);
-               if(warmup_stage)
-                       GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
-       }
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(warmup_stage)
-               return false;
-       if(player.frags == FRAGS_SPECTATOR)
-               return true;
-       if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
-               return true;
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-}
-
-void lms_RemovePlayer(entity player)
-{
-       static int quitters = 0;
-       float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
-       if (!player_rank)
-       {
-               int pl_cnt = 0;
-               FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
-               if (player.lms_spectate_warning != 2)
-               {
-                       if(IS_BOT_CLIENT(player))
-                               bot_clear(player);
-                       player.frags = FRAGS_LMS_LOSER;
-                       GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
-               }
-               else
-               {
-                       lms_lowest_lives = 999;
-                       FOREACH_CLIENT(true, {
-                               if (it.frags == FRAGS_LMS_LOSER)
-                               {
-                                       float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
-                                       if (it_rank > player_rank && it_rank <= 256)
-                                               GameRules_scoring_add(it, LMS_RANK, -1);
-                                       lms_lowest_lives = 0;
-                               }
-                               else if (it.frags != FRAGS_SPECTATOR)
-                               {
-                                       float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
-                                       if(tl < lms_lowest_lives)
-                                               lms_lowest_lives = tl;
-                               }
-                       });
-                       GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
-                       if(!warmup_stage)
-                       {
-                               GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
-                               ++quitters;
-                       }
-                       player.frags = FRAGS_LMS_LOSER;
-                       TRANSMUTE(Observer, player);
-               }
-               if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
-                       lms_lowest_lives = 0; // end the game now!
-       }
-
-       if(CS(player).killcount != FRAGS_SPECTATOR)
-               if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
-               else
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       lms_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if (!IS_PLAYER(player))
-               return true;
-
-       lms_RemovePlayer(player);
-       return true;  // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
-       {
-               GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
-               player.frags = FRAGS_SPECTATOR;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(lms, AutoJoinOnConnection)
-{
-       if(autocvar_g_campaign)
-               return false;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.deadflag == DEAD_DYING)
-               player.deadflag = DEAD_RESPAWNING;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
-{
-       if(autocvar_g_lms_regenerate)
-               return false;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
-{
-       // forbode!
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
-{
-       entity frag_target = M_ARGV(1, entity);
-
-       if (!warmup_stage)
-       {
-               // remove a life
-               int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
-               if(tl < lms_lowest_lives)
-                       lms_lowest_lives = tl;
-               if(tl <= 0)
-               {
-                       int pl_cnt = 0;
-                       FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
-                       if(IS_BOT_CLIENT(frag_target))
-                               bot_clear(frag_target);
-                       frag_target.frags = FRAGS_LMS_LOSER;
-                       GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
-               }
-       }
-       M_ARGV(2, float) = 0; // frag score
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, SetStartItems)
-{
-       start_items &= ~IT_UNLIMITED_AMMO;
-       start_health       = warmup_start_health       = cvar("g_lms_start_health");
-       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
-       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
-       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
-       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
-       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
-       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
-       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
-{
-       // don't clear player score
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
-{
-       entity definition = M_ARGV(0, entity);
-
-       if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
-       {
-               return false;
-       }
-       return true;
-}
-
-void lms_extralife(entity this)
-{
-       StartItem(this, ITEM_ExtraLife);
-}
-
-MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
-{
-       if (!autocvar_g_powerups) return false;
-       if (!autocvar_g_lms_extra_lives) return false;
-
-       entity ent = M_ARGV(0, entity);
-
-       // Can't use .itemdef here
-       if (ent.classname != "item_health_mega") return false;
-
-       entity e = spawn();
-       setthink(e, lms_extralife);
-
-       e.nextthink = time + 0.1;
-       e.spawnflags = ent.spawnflags;
-       e.noalign = ent.noalign;
-       setorigin(e, ent.origin);
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ItemTouch)
-{
-       entity item = M_ARGV(0, entity);
-       entity toucher = M_ARGV(1, entity);
-
-       if(item.itemdef == ITEM_ExtraLife)
-       {
-               Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
-               GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
-               return MUT_ITEMTOUCH_PICKUP;
-       }
-
-       return MUT_ITEMTOUCH_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               ++M_ARGV(0, int); // activerealplayers
-               ++M_ARGV(1, int); // realplayers
-       });
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(warmup_stage || player.lms_spectate_warning)
-       {
-               // for the forfeit message...
-               player.lms_spectate_warning = 2;
-       }
-       else
-       {
-               if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
-               {
-                       player.lms_spectate_warning = 1;
-                       sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
-               }
-               return MUT_SPECCMD_RETURN;
-       }
-       return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
-{
-       M_ARGV(0, float) = WinningCondition_LMS();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, WantWeapon)
-{
-       M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
-{
-       if(game_stopped)
-       if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
-               return true; // allow writing to this field in intermission as it is needed for newly joining players
-}
-
-void lms_Initialize()
-{
-       lms_lowest_lives = 9999;
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/lms/lms.qh b/qcsrc/common/gamemodes/gamemode/lms/lms.qh
deleted file mode 100644 (file)
index 2889220..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-#include <common/scores.qh>
-.float lms_spectate_warning;
-#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
-void lms_Initialize();
-
-REGISTER_MUTATOR(lms, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-        GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
-        GameRules_limit_lead(0);
-        GameRules_score_enabled(false);
-        GameRules_scoring(0, 0, 0, {
-            field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
-            field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
-        });
-
-               lms_Initialize();
-       }
-       return 0;
-}
-
-// lives related defs
-float lms_lowest_lives;
-float LMS_NewPlayerLives();
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc b/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc
new file mode 100644 (file)
index 0000000..493320c
--- /dev/null
@@ -0,0 +1,436 @@
+#include "sv_lms.qh"
+
+#include <common/mutators/mutator/instagib/items.qh>
+#include <server/campaign.qh>
+#include <server/command/_mod.qh>
+
+int autocvar_g_lms_extra_lives;
+bool autocvar_g_lms_join_anytime;
+int autocvar_g_lms_last_join;
+bool autocvar_g_lms_regenerate;
+
+// main functions
+float LMS_NewPlayerLives()
+{
+       float fl;
+       fl = autocvar_fraglimit;
+       if(fl == 0)
+               fl = 999;
+
+       // first player has left the game for dying too much? Nobody else can get in.
+       if(lms_lowest_lives < 1)
+               return 0;
+
+       if(!autocvar_g_lms_join_anytime)
+               if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
+                       return 0;
+
+       return bound(1, lms_lowest_lives, fl);
+}
+
+void ClearWinners();
+
+// LMS winning condition: game terminates if and only if there's at most one
+// one player who's living lives. Top two scores being equal cancels the time
+// limit.
+int WinningCondition_LMS()
+{
+       entity first_player = NULL;
+       int totalplayers = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if (!totalplayers)
+                       first_player = it;
+               ++totalplayers;
+       });
+
+       if (totalplayers)
+       {
+               if (totalplayers > 1)
+               {
+                       // two or more active players - continue with the game
+
+                       if (autocvar_g_campaign)
+                       {
+                               FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+                                       float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
+                                       if (!pl_lives)
+                                               return WINNING_YES; // human player lost, game over
+                                       break;
+                               });
+                       }
+               }
+               else
+               {
+                       // exactly one player?
+
+                       ClearWinners();
+                       SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
+
+                       if (LMS_NewPlayerLives())
+                       {
+                               // game still running (that is, nobody got removed from the game by a frag yet)? then continue
+                               return WINNING_NO;
+                       }
+                       else
+                       {
+                               // a winner!
+                               // and assign him his first place
+                               GameRules_scoring_add(first_player, LMS_RANK, 1);
+                               if(warmup_stage)
+                                       return WINNING_NO;
+                               else
+                                       return WINNING_YES;
+                       }
+               }
+       }
+       else
+       {
+               // nobody is playing at all...
+               if (LMS_NewPlayerLives())
+               {
+                       // wait for players...
+               }
+               else
+               {
+                       // SNAFU (maybe a draw game?)
+                       ClearWinners();
+                       LOG_TRACE("No players, ending game.");
+                       return WINNING_YES;
+               }
+       }
+
+       // When we get here, we have at least two players who are actually LIVING,
+       // now check if the top two players have equal score.
+       WinningConditionHelper(NULL);
+
+       ClearWinners();
+       if(WinningConditionHelper_winner)
+               WinningConditionHelper_winner.winning = true;
+       if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
+               return WINNING_NEVER;
+
+       // Top two have different scores? Way to go for our beloved TIMELIMIT!
+       return WINNING_NO;
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(lms, reset_map_global)
+{
+       lms_lowest_lives = 999;
+}
+
+MUTATOR_HOOKFUNCTION(lms, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               TRANSMUTE(Player, it);
+               it.frags = FRAGS_PLAYER;
+               GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
+               PutClientInServer(it);
+       });
+}
+
+MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.frags == FRAGS_SPECTATOR)
+               TRANSMUTE(Observer, player);
+       else
+       {
+               float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
+               if(tl < lms_lowest_lives)
+                       lms_lowest_lives = tl;
+               if(tl <= 0)
+                       TRANSMUTE(Observer, player);
+               if(warmup_stage)
+                       GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
+       }
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(warmup_stage)
+               return false;
+       if(player.frags == FRAGS_SPECTATOR)
+               return true;
+       if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
+               return true;
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+}
+
+void lms_RemovePlayer(entity player)
+{
+       static int quitters = 0;
+       float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
+       if (!player_rank)
+       {
+               int pl_cnt = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+               if (player.lms_spectate_warning != 2)
+               {
+                       if(IS_BOT_CLIENT(player))
+                               bot_clear(player);
+                       player.frags = FRAGS_LMS_LOSER;
+                       GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
+               }
+               else
+               {
+                       lms_lowest_lives = 999;
+                       FOREACH_CLIENT(true, {
+                               if (it.frags == FRAGS_LMS_LOSER)
+                               {
+                                       float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
+                                       if (it_rank > player_rank && it_rank <= 256)
+                                               GameRules_scoring_add(it, LMS_RANK, -1);
+                                       lms_lowest_lives = 0;
+                               }
+                               else if (it.frags != FRAGS_SPECTATOR)
+                               {
+                                       float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
+                                       if(tl < lms_lowest_lives)
+                                               lms_lowest_lives = tl;
+                               }
+                       });
+                       GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
+                       if(!warmup_stage)
+                       {
+                               GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
+                               ++quitters;
+                       }
+                       player.frags = FRAGS_LMS_LOSER;
+                       TRANSMUTE(Observer, player);
+               }
+               if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
+                       lms_lowest_lives = 0; // end the game now!
+       }
+
+       if(CS(player).killcount != FRAGS_SPECTATOR)
+               if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
+               else
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       lms_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (!IS_PLAYER(player))
+               return true;
+
+       lms_RemovePlayer(player);
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
+       {
+               GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
+               player.frags = FRAGS_SPECTATOR;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(lms, AutoJoinOnConnection)
+{
+       if(autocvar_g_campaign)
+               return false;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.deadflag == DEAD_DYING)
+               player.deadflag = DEAD_RESPAWNING;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
+{
+       if(autocvar_g_lms_regenerate)
+               return false;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
+{
+       // forbode!
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
+{
+       entity frag_target = M_ARGV(1, entity);
+
+       if (!warmup_stage)
+       {
+               // remove a life
+               int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
+               if(tl < lms_lowest_lives)
+                       lms_lowest_lives = tl;
+               if(tl <= 0)
+               {
+                       int pl_cnt = 0;
+                       FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+                       if(IS_BOT_CLIENT(frag_target))
+                               bot_clear(frag_target);
+                       frag_target.frags = FRAGS_LMS_LOSER;
+                       GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
+               }
+       }
+       M_ARGV(2, float) = 0; // frag score
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, SetStartItems)
+{
+       start_items &= ~IT_UNLIMITED_AMMO;
+       start_health       = warmup_start_health       = cvar("g_lms_start_health");
+       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
+       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
+       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
+       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
+       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
+       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
+{
+       // don't clear player score
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
+{
+       entity definition = M_ARGV(0, entity);
+
+       if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
+       {
+               return false;
+       }
+       return true;
+}
+
+void lms_extralife(entity this)
+{
+       StartItem(this, ITEM_ExtraLife);
+}
+
+MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
+{
+       if (!autocvar_g_powerups) return false;
+       if (!autocvar_g_lms_extra_lives) return false;
+
+       entity ent = M_ARGV(0, entity);
+
+       // Can't use .itemdef here
+       if (ent.classname != "item_health_mega") return false;
+
+       entity e = spawn();
+       setthink(e, lms_extralife);
+
+       e.nextthink = time + 0.1;
+       e.spawnflags = ent.spawnflags;
+       e.noalign = ent.noalign;
+       setorigin(e, ent.origin);
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ItemTouch)
+{
+       entity item = M_ARGV(0, entity);
+       entity toucher = M_ARGV(1, entity);
+
+       if(item.itemdef == ITEM_ExtraLife)
+       {
+               Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
+               GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
+               return MUT_ITEMTOUCH_PICKUP;
+       }
+
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               ++M_ARGV(0, int); // activerealplayers
+               ++M_ARGV(1, int); // realplayers
+       });
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(warmup_stage || player.lms_spectate_warning)
+       {
+               // for the forfeit message...
+               player.lms_spectate_warning = 2;
+       }
+       else
+       {
+               if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
+               {
+                       player.lms_spectate_warning = 1;
+                       sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
+               }
+               return MUT_SPECCMD_RETURN;
+       }
+       return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
+{
+       M_ARGV(0, float) = WinningCondition_LMS();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, WantWeapon)
+{
+       M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
+{
+       if(game_stopped)
+       if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
+               return true; // allow writing to this field in intermission as it is needed for newly joining players
+}
+
+void lms_Initialize()
+{
+       lms_lowest_lives = 9999;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qh b/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qh
new file mode 100644 (file)
index 0000000..256620a
--- /dev/null
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+.float lms_spectate_warning;
+#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
+void lms_Initialize();
+
+REGISTER_MUTATOR(lms, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+        GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
+        GameRules_limit_lead(0);
+        GameRules_score_enabled(false);
+        GameRules_scoring(0, 0, 0, {
+            field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
+            field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
+        });
+
+               lms_Initialize();
+       }
+       return 0;
+}
+
+// lives related defs
+float lms_lowest_lives;
+float LMS_NewPlayerLives();
index 487012aa50902e7a834fdd708a7c0e45bb2928cc..65c5bcd8a0189530b2baf7eeba38a2ebcacdc272 100644 (file)
@@ -7,7 +7,7 @@ REGISTER_MUTATOR(cl_nb, true);
 
 MUTATOR_HOOKFUNCTION(cl_nb, WantEventchase)
 {
-       if(autocvar_cl_eventchase_nexball && gametype == MAPINFO_TYPE_NEXBALL && !(WepSet_GetFromStat() & WEPSET(NEXBALL)))
+       if(autocvar_cl_eventchase_nexball && ISGAMETYPE(NEXBALL) && !(WepSet_GetFromStat() & WEPSET(NEXBALL)))
                return true;
        return false;
 }
@@ -635,7 +635,7 @@ void SpawnGoal(entity this)
 
        EXACTTRIGGER_INIT;
 
-       if(this.team != GOAL_OUT && Team_TeamToNumber(this.team) != -1)
+       if(this.team != GOAL_OUT && Team_IsValidTeam(this.team))
        {
                entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE);
                wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0');
@@ -920,7 +920,7 @@ MUTATOR_HOOKFUNCTION(nb, ItemTouch)
        return MUT_ITEMTOUCH_CONTINUE;
 }
 
-MUTATOR_HOOKFUNCTION(nb, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(nb, TeamBalance_CheckAllowedTeams)
 {
        M_ARGV(1, string) = "nexball_team";
        return true;
index 838d0dd7e9cf07e0be6fc7e4ebf894613e452f55..02b57b2ca879bac2ddc4f54fb70327902b7e5de3 100644 (file)
@@ -1,7 +1,7 @@
 #include "sv_weapon.qh"
 
-void W_Nexball_Attack(entity actor, .entity weaponentity, float t);
-void W_Nexball_Attack2(entity actor, .entity weaponentity);
+void W_Nexball_Attack(Weapon thiswep, entity actor, .entity weaponentity, float t);
+void W_Nexball_Attack2(Weapon thiswep, entity actor, .entity weaponentity);
 vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity);
 
 METHOD(BallStealer, wr_think, void(BallStealer thiswep, entity actor, .entity weaponentity, int fire))
@@ -18,19 +18,19 @@ METHOD(BallStealer, wr_think, void(BallStealer thiswep, entity actor, .entity we
             }
             else
             {
-                W_Nexball_Attack(actor, weaponentity, -1);
+                W_Nexball_Attack(thiswep, actor, weaponentity, -1);
                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
             }
     if(fire & 2)
         if(weapon_prepareattack(thiswep, actor, weaponentity, true, autocvar_g_balance_nexball_secondary_refire))
         {
-            W_Nexball_Attack2(actor, weaponentity);
+            W_Nexball_Attack2(thiswep, actor, weaponentity);
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, autocvar_g_balance_nexball_secondary_animtime, w_ready);
         }
 
     if(!(fire & 1) && STAT(NB_METERSTART, actor) && actor.ballcarried)
     {
-        W_Nexball_Attack(actor, weaponentity, time - STAT(NB_METERSTART, actor));
+        W_Nexball_Attack(thiswep, actor, weaponentity, time - STAT(NB_METERSTART, actor));
         // DropBall or stealing will set metertime back to 0
         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
     }
@@ -105,7 +105,7 @@ void W_Nexball_Touch(entity this, entity toucher)
        delete(this);
 }
 
-void W_Nexball_Attack(entity actor, .entity weaponentity, float t)
+void W_Nexball_Attack(Weapon thiswep, entity actor, .entity weaponentity, float t)
 {
        entity ball;
        float mul, mi, ma;
@@ -141,7 +141,7 @@ void W_Nexball_Attack(entity actor, .entity weaponentity, float t)
        //TODO: use the speed_up cvar too ??
 }
 
-void W_Nexball_Attack2(entity actor, .entity weaponentity)
+void W_Nexball_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 {
        if(actor.ballcarried.enemy)
        {
index f0217de7e6cd0d238d0a1ae7a785c88209c42fb0..b4c4dc68fed96fa6a6c32b7e47ca2cb0b3a7957f 100644 (file)
@@ -27,7 +27,6 @@ float autocvar_g_onslaught_teleport_radius;
 float autocvar_g_onslaught_spawn_choose;
 float autocvar_g_onslaught_click_radius;
 
-void FixSize(entity e);
 entity cam;
 
 // =======================
@@ -114,12 +113,6 @@ void ons_CaptureShield_Spawn(entity generator, bool is_generator)
 // Junk Pile
 // ==========
 
-void setmodel_fixsize(entity e, Model m)
-{
-       setmodel(e, m);
-       FixSize(e);
-}
-
 void onslaught_updatelinks()
 {
        entity l;
@@ -438,7 +431,7 @@ void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker
 
                this.owner.waslinked = this.owner.islinked;
                if(this.owner.model != "models/onslaught/controlpoint_pad.md3")
-                       setmodel_fixsize(this.owner, MDL_ONS_CP_PAD1);
+                       setmodel(this.owner, MDL_ONS_CP_PAD1);
                //setsize(this, '-32 -32 0', '32 32 8');
 
                delete(this);
@@ -578,7 +571,7 @@ void ons_ControlPoint_Icon_BuildThink(entity this)
                this.SendFlags |= CPSF_SETUP;
        }
        if(this.owner.model != MDL_ONS_CP_PAD2.model_str())
-               setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2);
+               setmodel(this.owner, MDL_ONS_CP_PAD2);
 
        if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
                Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1);
@@ -737,7 +730,7 @@ void ons_ControlPoint_Reset(entity this)
        setthink(this, ons_ControlPoint_Think);
        this.ons_toucher = NULL;
        this.nextthink = time + ONS_CP_THINKRATE;
-       setmodel_fixsize(this, MDL_ONS_CP_PAD1);
+       setmodel(this, MDL_ONS_CP_PAD1);
 
        WaypointSprite_UpdateMaxHealth(this.sprite, 0);
        WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
@@ -781,7 +774,7 @@ void ons_ControlPoint_Setup(entity cp)
        if(cp.message == "") { cp.message = "a"; }
 
        // appearence
-       setmodel_fixsize(cp, MDL_ONS_CP_PAD1);
+       setmodel(cp, MDL_ONS_CP_PAD1);
 
        // control point placement
        if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
@@ -1113,46 +1106,52 @@ int total_generators;
 void Onslaught_count_generators()
 {
        entity e;
-       total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
+       total_generators = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+       }
        for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
        {
                ++total_generators;
-               redowned += (e.team == NUM_TEAM_1 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
-               blueowned += (e.team == NUM_TEAM_2 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
-               yellowowned += (e.team == NUM_TEAM_3 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
-               pinkowned += (e.team == NUM_TEAM_4 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+               if (GetResourceAmount(e, RESOURCE_HEALTH) < 1)
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(e);
+               int num_control_points = Team_GetNumberOfControlPoints(team_);
+               ++num_control_points;
+               Team_SetNumberOfControlPoints(team_, num_control_points);
        }
 }
 
 int Onslaught_GetWinnerTeam()
 {
        int winner_team = 0;
-       if(redowned > 0)
-               winner_team = NUM_TEAM_1;
-       if(blueowned > 0)
+       if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) >= 1)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
+               winner_team = NUM_TEAM_1;
        }
-       if(yellowowned > 0)
+       for (int i = 2; i <= NUM_TEAMS; ++i)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
+               if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) >= 1)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
        }
-       if(pinkowned > 0)
+       if (winner_team)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
                return winner_team;
+       }
        return -1; // no generators left?
 }
 
 void nades_Clear(entity e);
 
-#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
 bool Onslaught_CheckWinner()
 {
        if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
@@ -1198,8 +1197,10 @@ bool Onslaught_CheckWinner()
 
        Onslaught_count_generators();
 
-       if(ONS_OWNED_GENERATORS_OK())
+       if (Team_GetNumberOfTeamsWithControlPoints() > 1)
+       {
                return 0;
+       }
 
        int winner_team = Onslaught_GetWinnerTeam();
 
@@ -1256,71 +1257,26 @@ void Onslaught_RoundStart()
 
 // NOTE: LEGACY CODE, needs to be re-written!
 
-void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius)
-{
-       bool needarmor = false, needweapons = false;
-
-       // Needs armor/health?
-       if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
-               needarmor = true;
-
-       // Needs weapons?
-       int c = 0;
-       FOREACH(Weapons, it != WEP_Null, {
-               if(STAT(WEAPONS, this) & (it.m_wepset))
-               if(++c >= 4)
-                       break;
-       });
-
-       if(c<4)
-               needweapons = true;
-
-       if(!needweapons && !needarmor)
-               return;
-
-       LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons));
-       LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor));
-
-       // See what is around
-       IL_EACH(g_items, it.bot_pickup,
-       {
-               // gather health and armor only
-               if (it.solid)
-               if ( ((GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR)) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
-               if (vdist(it.origin - org, <, sradius))
-               {
-                       int t = it.bot_pickupevalfunc(this, it);
-                       if (t > 0)
-                               navigation_routerating(this, it, t * ratingscale, 500);
-               }
-       });
-}
-
 void havocbot_role_ons_setrole(entity this, int role)
 {
-       LOG_DEBUG(this.netname," switched to ");
        switch(role)
        {
                case HAVOCBOT_ONS_ROLE_DEFENSE:
-                       LOG_DEBUG("defense");
+                       LOG_DEBUG(this.netname, " switched to defense");
                        this.havocbot_role = havocbot_role_ons_defense;
-                       this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_ONS_ROLE_ASSISTANT:
-                       LOG_DEBUG("assistant");
+                       LOG_DEBUG(this.netname, " switched to assistant");
                        this.havocbot_role = havocbot_role_ons_assistant;
-                       this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_ONS_ROLE_OFFENSE:
-                       LOG_DEBUG("offense");
+                       LOG_DEBUG(this.netname, " switched to offense");
                        this.havocbot_role = havocbot_role_ons_offense;
-                       this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
        }
-       LOG_DEBUG("");
 }
 
 void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale)
@@ -1345,9 +1301,9 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
 
                // Count team mates interested in this control point
                // (easier and cleaner than keeping counters per cp and teams)
-               FOREACH_CLIENT(IS_PLAYER(it), {
+               FOREACH_CLIENT(it != this && IS_PLAYER(it), {
                        if(SAME_TEAM(it, this))
-                       if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
+                       if(it.havocbot_role == havocbot_role_ons_offense)
                        if(it.havocbot_ons_target == cp2)
                                ++c;
                });
@@ -1358,7 +1314,7 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
        }
 
        // We'll consider only the best case
-       bestvalue = 99999999999;
+       bestvalue = FLOAT_MAX;
        cp = NULL;
        for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
        {
@@ -1384,23 +1340,21 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
                // Rate waypoints near it
                found = false;
                best = NULL;
-               bestvalue = 99999999999;
-               for(radius=0; radius<1000 && !found; radius+=500)
+               bestvalue = FLOAT_MAX;
+               for (radius = 500; radius <= 1000 && !found; radius += 500)
                {
-                       for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+                       IL_EACH(g_waypoints, vdist(cp.origin - it.origin, <, radius),
                        {
-                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
-                               if(wp.classname=="waypoint")
-                               if(checkpvs(wp.origin,cp))
+                               if (!(it.wpflags & WAYPOINTFLAG_GENERATED) && checkpvs(it.origin, cp))
                                {
                                        found = true;
-                                       if(wp.cnt<bestvalue)
+                                       if (it.cnt < bestvalue)
                                        {
-                                               best = wp;
-                                               bestvalue = wp.cnt;
+                                               best = it;
+                                               bestvalue = it.cnt;
                                        }
                                }
-                       }
+                       });
                }
 
                if(best)
@@ -1423,22 +1377,7 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
        {
                // Should be touched
                LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
-               found = false;
-
-               // Look for auto generated waypoint
-               if (!bot_waypoints_for_items)
-               for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
-               {
-                       if(wp.classname=="waypoint")
-                       {
-                               navigation_routerating(this, wp, ratingscale, 10000);
-                               found = true;
-                       }
-               }
-
-               // Nothing found, rate the controlpoint itself
-               if (!found)
-                       navigation_routerating(this, cp, ratingscale, 10000);
+               navigation_routerating(this, cp, ratingscale * 2, 10000);
        }
 }
 
@@ -1446,7 +1385,7 @@ bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
 {
        entity g, wp, bestwp;
        bool found;
-       int best;
+       int bestvalue;
 
        for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
        {
@@ -1457,21 +1396,20 @@ bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
                // Rate waypoints near it
                found = false;
                bestwp = NULL;
-               best = 99999999999;
+               bestvalue = FLOAT_MAX;
 
-               for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+               IL_EACH(g_waypoints, vdist(g.origin - it.origin, <, 400),
                {
-                       if(wp.classname=="waypoint")
-                       if(checkpvs(wp.origin,g))
+                       if (checkpvs(it.origin, g))
                        {
                                found = true;
-                               if(wp.cnt<best)
+                               if (it.cnt < bestvalue)
                                {
-                                       bestwp = wp;
-                                       best = wp.cnt;
+                                       bestwp = it;
+                                       bestvalue = it.cnt;
                                }
                        }
-               }
+               });
 
                if(bestwp)
                {
@@ -1524,9 +1462,9 @@ void havocbot_role_ons_offense(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
-               if(!havocbot_goalrating_ons_generator_attack(this, 20000))
-                       havocbot_goalrating_ons_controlpoints_attack(this, 20000);
-               havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
+               if(!havocbot_goalrating_ons_generator_attack(this, 10000))
+                       havocbot_goalrating_ons_controlpoints_attack(this, 10000);
+               havocbot_goalrating_items(this, 25000, this.origin, 10000);
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
@@ -1960,17 +1898,14 @@ MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole)
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ons, CheckAllowedTeams)
+MUTATOR_HOOKFUNCTION(ons, TeamBalance_CheckAllowedTeams)
 {
        // onslaught is special
        for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
        {
-               switch(tmp_entity.team)
+               if (Team_IsValidTeam(tmp_entity.team))
                {
-                       case NUM_TEAM_1: c1 = 0; break;
-                       case NUM_TEAM_2: c2 = 0; break;
-                       case NUM_TEAM_3: c3 = 0; break;
-                       case NUM_TEAM_4: c4 = 0; break;
+                       M_ARGV(0, float) |= Team_TeamToBit(tmp_entity.team);
                }
        }
 
@@ -2200,12 +2135,9 @@ spawnfunc(onslaught_generator)
 // scoreboard setup
 void ons_ScoreRules()
 {
-       CheckAllowedTeams(NULL);
-       int teams = 0;
-       if(c1 >= 0) teams |= BIT(0);
-       if(c2 >= 0) teams |= BIT(1);
-       if(c3 >= 0) teams |= BIT(2);
-       if(c4 >= 0) teams |= BIT(3);
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       int teams = TeamBalance_GetAllowedTeams(balance);
+       TeamBalance_Destroy(balance);
        GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
            field_team(ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY);
            field(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
index 5c7fd469735b1e38cad2abdf8e11165d9ffb3d66..7a9b192a5a7b041e313c807dd464b04d3e0c3e98 100644 (file)
@@ -80,7 +80,6 @@ const int HAVOCBOT_ONS_ROLE_OFFENSE   = 8;
 
 .entity havocbot_ons_target;
 
-.int havocbot_role_flags;
 .float havocbot_attack_time;
 
 void havocbot_role_ons_defense(entity this);
index 73f34a583374eb177ee4d54c8830d2acac89dba8..5ed2c9598192123438f5821ffcc251858853bc50 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/race/race.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/race/sv_race.qc>
+#endif
index 1158df561696279a06fd0b1ee0e47f4b473bee23..1e76e7af30bc562380d86abfc7abf9d2894bee89 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/race/race.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/race/sv_race.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qc b/qcsrc/common/gamemodes/gamemode/race/race.qc
deleted file mode 100644 (file)
index c98e1b6..0000000
+++ /dev/null
@@ -1,492 +0,0 @@
-#include "race.qh"
-
-// TODO: sv_race
-#ifdef SVQC
-#include <server/race.qh>
-
-#define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
-float autocvar_g_race_qualifying_timelimit;
-float autocvar_g_race_qualifying_timelimit_override;
-int autocvar_g_race_teams;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_race(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               bool raw_touch_check = true;
-               int cp = this.race_checkpoint;
-
-               LABEL(search_racecheckpoints)
-               IL_EACH(g_racecheckpoints, true,
-               {
-                       if(it.cnt == cp || cp == -1)
-                       {
-                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
-                               // e.g. checkpoint in front of Stormkeep's warpzone
-                               // the same workaround is applied in CTS game mode
-                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
-                               {
-                                       cp = race_NextCheckpoint(cp);
-                                       raw_touch_check = false;
-                                       goto search_racecheckpoints;
-                               }
-                               navigation_routerating(this, it, 1000000, 5000);
-                       }
-               });
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void race_ScoreRules()
-{
-    GameRules_score_enabled(false);
-       GameRules_scoring(race_teams, 0, 0, {
-        if (race_teams) {
-            field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
-        } else if (g_race_qualifying) {
-            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-        } else {
-            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
-        }
-       });
-}
-
-void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-float WinningCondition_Race(float fraglimit)
-{
-       float wc;
-       float n, c;
-
-       n = 0;
-       c = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               ++n;
-               if(CS(it).race_completed)
-                       ++c;
-       });
-       if(n && (n == c))
-               return WINNING_YES;
-       wc = WinningCondition_Scores(fraglimit, 0);
-
-       // ALWAYS initiate overtime, unless EVERYONE has finished the race!
-       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
-       // do NOT support equality when the laps are all raced!
-               return WINNING_STARTSUDDENDEATHOVERTIME;
-       else
-               return WINNING_NEVER;
-}
-
-float WinningCondition_QualifyingThenRace(float limit)
-{
-       float wc;
-       wc = WinningCondition_Scores(limit, 0);
-
-       // NEVER initiate overtime
-       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
-       {
-               return WINNING_YES;
-       }
-
-       return wc;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientKill)
-{
-       if(g_race_qualifying)
-               M_ARGV(1, float) = 0; // killtime
-}
-
-MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(autocvar_g_allow_checkpoints)
-               race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
-{
-       entity player = M_ARGV(0, entity);
-       float dt = M_ARGV(1, float);
-
-       player.race_movetime_frac += dt;
-       float f = floor(player.race_movetime_frac);
-       player.race_movetime_frac -= f;
-       player.race_movetime_count += f;
-       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
-       if(IS_PLAYER(player))
-       {
-               if (player.race_penalty)
-                       if (time > player.race_penalty)
-                               player.race_penalty = 0;
-               if(player.race_penalty)
-               {
-                       player.velocity = '0 0 0';
-                       set_movetype(player, MOVETYPE_NONE);
-                       player.disableclientprediction = 2;
-               }
-       }
-#endif
-
-       // force kbd movement for fairness
-       float wishspeed;
-       vector wishvel;
-
-       // if record times matter
-       // ensure nothing EVIL is being done (i.e. div0_evade)
-       // this hinders joystick users though
-       // but it still gives SOME analog control
-       wishvel.x = fabs(CS(player).movement.x);
-       wishvel.y = fabs(CS(player).movement.y);
-       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
-       {
-               wishvel.z = 0;
-               wishspeed = vlen(wishvel);
-               if(wishvel.x >= 2 * wishvel.y)
-               {
-                       // pure X motion
-                       if(CS(player).movement.x > 0)
-                               CS(player).movement_x = wishspeed;
-                       else
-                               CS(player).movement_x = -wishspeed;
-                       CS(player).movement_y = 0;
-               }
-               else if(wishvel.y >= 2 * wishvel.x)
-               {
-                       // pure Y motion
-                       CS(player).movement_x = 0;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = wishspeed;
-                       else
-                               CS(player).movement_y = -wishspeed;
-               }
-               else
-               {
-                       // diagonal
-                       if(CS(player).movement.x > 0)
-                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, reset_map_global)
-{
-       float s;
-
-       Score_NicePrint(NULL);
-
-       race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
-
-       FOREACH_CLIENT(true, {
-               if(it.race_place)
-               {
-                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
-                       if(!s)
-                               it.race_place = 0;
-               }
-               race_EventLog(ftos(it.race_place), it);
-       });
-
-       if(g_race_qualifying == 2)
-       {
-               g_race_qualifying = 0;
-               independent_players = 0;
-               cvar_set("fraglimit", ftos(race_fraglimit));
-               cvar_set("leadlimit", ftos(race_leadlimit));
-               cvar_set("timelimit", ftos(race_timelimit));
-               race_ScoreRules();
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-
-       string rr = RACE_RECORD;
-
-       if(IS_REAL_CLIENT(player))
-       {
-               msg_entity = player;
-               race_send_recordtime(MSG_ONE);
-               race_send_speedaward(MSG_ONE);
-
-               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
-               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
-               race_send_speedaward_alltimebest(MSG_ONE);
-
-               float i;
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(g_race_qualifying)
-       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
-               player.frags = FRAGS_LMS_LOSER;
-       else
-               player.frags = FRAGS_SPECTATOR;
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-       entity spawn_spot = M_ARGV(1, entity);
-
-       if(spawn_spot.target == "")
-               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
-               race_PreparePlayer(player);
-
-       // if we need to respawn, do it right
-       player.race_respawn_checkpoint = player.race_checkpoint;
-       player.race_respawn_spotref = spawn_spot;
-
-       player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(IS_PLAYER(player))
-       if(!game_stopped)
-       {
-               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
-                       race_PreparePlayer(player);
-               else // respawn
-                       race_RetractPlayer(player);
-
-               race_AbandonRaceCheck(player);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-       race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       bot.havocbot_role = havocbot_role_race;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-
-       if (!IS_OBSERVER(player))
-       {
-               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
-               {
-                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
-                       speedaward_holder = player.netname;
-                       speedaward_uid = player.crypto_idfp;
-                       speedaward_lastupdate = time;
-               }
-               if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
-               {
-                       string rr = RACE_RECORD;
-                       race_send_speedaward(MSG_ALL);
-                       speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
-                       {
-                               speedaward_alltimebest = speedaward_speed;
-                               speedaward_alltimebest_holder = speedaward_holder;
-                               speedaward_alltimebest_uid = speedaward_uid;
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
-                               race_send_speedaward_alltimebest(MSG_ALL);
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
-{
-       if(g_race_qualifying)
-               return true; // in qualifying, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = race_teams;
-}
-
-MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
-{
-       // announce remaining frags if not in qualifying mode
-       if(!g_race_qualifying)
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetRecords)
-{
-       int record_page = M_ARGV(0, int);
-       string ret_string = M_ARGV(1, string);
-
-       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
-       {
-               if(MapInfo_Get_ByID(i))
-               {
-                       float r = race_readTime(MapInfo_Map_bspname, 1);
-
-                       if(!r)
-                               continue;
-
-                       string h = race_readName(MapInfo_Map_bspname, 1);
-                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
-               }
-       }
-
-       M_ARGV(1, string) = ret_string;
-}
-
-MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
-{
-       return true; // doesn't work so well
-}
-
-MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
-{
-       entity player = M_ARGV(0, entity);
-
-       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
-{
-       float checkrules_timelimit = M_ARGV(1, float);
-       float checkrules_fraglimit = M_ARGV(2, float);
-
-       if(checkrules_timelimit >= 0)
-       {
-               if(!g_race_qualifying)
-               {
-                       M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
-                       return true;
-               }
-               else if(g_race_qualifying == 2)
-               {
-                       M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
-                       return true;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
-{
-       if(g_race_qualifying == 2)
-               warmup_stage = 0;
-}
-
-void race_Initialize()
-{
-       race_ScoreRules();
-       if(g_race_qualifying == 2)
-               warmup_stage = 0;
-}
-
-void rc_SetLimits()
-{
-       int fraglimit_override, leadlimit_override;
-       float timelimit_override, qualifying_override;
-
-       if(autocvar_g_race_teams)
-       {
-               GameRules_teams(true);
-               race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
-       }
-       else
-               race_teams = 0;
-
-       qualifying_override = autocvar_g_race_qualifying_timelimit_override;
-       fraglimit_override = autocvar_g_race_laps_limit;
-       leadlimit_override = 0; // currently not supported by race
-       timelimit_override = autocvar_timelimit_override;
-
-       float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
-
-       if(autocvar_g_campaign)
-       {
-               g_race_qualifying = 1;
-               independent_players = 1;
-       }
-       else if(want_qualifying)
-       {
-               g_race_qualifying = 2;
-               independent_players = 1;
-               race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
-               race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
-               race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
-               qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
-               fraglimit_override = 0;
-               leadlimit_override = 0;
-               timelimit_override = qualifying_override;
-       }
-       else
-               g_race_qualifying = 0;
-    GameRules_limit_score(fraglimit_override);
-    GameRules_limit_lead(leadlimit_override);
-    GameRules_limit_time(timelimit_override);
-    GameRules_limit_time_qualifying(qualifying_override);
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qh b/qcsrc/common/gamemodes/gamemode/race/race.qh
deleted file mode 100644 (file)
index ad966af..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-void rc_SetLimits();
-void race_Initialize();
-
-REGISTER_MUTATOR(rc, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               rc_SetLimits();
-
-               race_Initialize();
-       }
-       return 0;
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/race/sv_race.qc b/qcsrc/common/gamemodes/gamemode/race/sv_race.qc
new file mode 100644 (file)
index 0000000..31309f2
--- /dev/null
@@ -0,0 +1,490 @@
+#include "sv_race.qh"
+
+#include <server/race.qh>
+
+#define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
+float autocvar_g_race_qualifying_timelimit;
+float autocvar_g_race_qualifying_timelimit_override;
+int autocvar_g_race_teams;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_race(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
+               IL_EACH(g_racecheckpoints, true,
+               {
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in CTS game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
+                               navigation_routerating(this, it, 1000000, 5000);
+                       }
+               });
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void race_ScoreRules()
+{
+    GameRules_score_enabled(false);
+       GameRules_scoring(race_teams, 0, 0, {
+        if (race_teams) {
+            field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+        } else if (g_race_qualifying) {
+            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+        } else {
+            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+        }
+       });
+}
+
+void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+float WinningCondition_Race(float fraglimit)
+{
+       float wc;
+       float n, c;
+
+       n = 0;
+       c = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               ++n;
+               if(CS(it).race_completed)
+                       ++c;
+       });
+       if(n && (n == c))
+               return WINNING_YES;
+       wc = WinningCondition_Scores(fraglimit, 0);
+
+       // ALWAYS initiate overtime, unless EVERYONE has finished the race!
+       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+       // do NOT support equality when the laps are all raced!
+               return WINNING_STARTSUDDENDEATHOVERTIME;
+       else
+               return WINNING_NEVER;
+}
+
+float WinningCondition_QualifyingThenRace(float limit)
+{
+       float wc;
+       wc = WinningCondition_Scores(limit, 0);
+
+       // NEVER initiate overtime
+       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+       {
+               return WINNING_YES;
+       }
+
+       return wc;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientKill)
+{
+       if(g_race_qualifying)
+               M_ARGV(1, float) = 0; // killtime
+}
+
+MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(autocvar_g_allow_checkpoints)
+               race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
+{
+       entity player = M_ARGV(0, entity);
+       float dt = M_ARGV(1, float);
+
+       player.race_movetime_frac += dt;
+       float f = floor(player.race_movetime_frac);
+       player.race_movetime_frac -= f;
+       player.race_movetime_count += f;
+       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+#ifdef SVQC
+       if(IS_PLAYER(player))
+       {
+               if (player.race_penalty)
+                       if (time > player.race_penalty)
+                               player.race_penalty = 0;
+               if(player.race_penalty)
+               {
+                       player.velocity = '0 0 0';
+                       set_movetype(player, MOVETYPE_NONE);
+                       player.disableclientprediction = 2;
+               }
+       }
+#endif
+
+       // force kbd movement for fairness
+       float wishspeed;
+       vector wishvel;
+
+       // if record times matter
+       // ensure nothing EVIL is being done (i.e. div0_evade)
+       // this hinders joystick users though
+       // but it still gives SOME analog control
+       wishvel.x = fabs(CS(player).movement.x);
+       wishvel.y = fabs(CS(player).movement.y);
+       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
+       {
+               wishvel.z = 0;
+               wishspeed = vlen(wishvel);
+               if(wishvel.x >= 2 * wishvel.y)
+               {
+                       // pure X motion
+                       if(CS(player).movement.x > 0)
+                               CS(player).movement_x = wishspeed;
+                       else
+                               CS(player).movement_x = -wishspeed;
+                       CS(player).movement_y = 0;
+               }
+               else if(wishvel.y >= 2 * wishvel.x)
+               {
+                       // pure Y motion
+                       CS(player).movement_x = 0;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = wishspeed;
+                       else
+                               CS(player).movement_y = -wishspeed;
+               }
+               else
+               {
+                       // diagonal
+                       if(CS(player).movement.x > 0)
+                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, reset_map_global)
+{
+       float s;
+
+       Score_NicePrint(NULL);
+
+       race_ClearRecords();
+       PlayerScore_Sort(race_place, 0, 1, 0);
+
+       FOREACH_CLIENT(true, {
+               if(it.race_place)
+               {
+                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+                       if(!s)
+                               it.race_place = 0;
+               }
+               race_EventLog(ftos(it.race_place), it);
+       });
+
+       if(g_race_qualifying == 2)
+       {
+               g_race_qualifying = 0;
+               independent_players = 0;
+               cvar_set("fraglimit", ftos(race_fraglimit));
+               cvar_set("leadlimit", ftos(race_leadlimit));
+               cvar_set("timelimit", ftos(race_timelimit));
+               race_ScoreRules();
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+
+       string rr = RACE_RECORD;
+
+       if(IS_REAL_CLIENT(player))
+       {
+               msg_entity = player;
+               race_send_recordtime(MSG_ONE);
+               race_send_speedaward(MSG_ONE);
+
+               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
+               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
+               race_send_speedaward_alltimebest(MSG_ONE);
+
+               float i;
+               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+               race_send_rankings_cnt(MSG_ONE);
+               for (i = 1; i <= m; ++i)
+               {
+                       race_SendRankings(i, 0, 0, MSG_ONE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(g_race_qualifying)
+       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+               player.frags = FRAGS_LMS_LOSER;
+       else
+               player.frags = FRAGS_SPECTATOR;
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       entity spawn_spot = M_ARGV(1, entity);
+
+       if(spawn_spot.target == "")
+               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+               race_PreparePlayer(player);
+
+       // if we need to respawn, do it right
+       player.race_respawn_checkpoint = player.race_checkpoint;
+       player.race_respawn_spotref = spawn_spot;
+
+       player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(IS_PLAYER(player))
+       if(!game_stopped)
+       {
+               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+                       race_PreparePlayer(player);
+               else // respawn
+                       race_RetractPlayer(player);
+
+               race_AbandonRaceCheck(player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       race_AbandonRaceCheck(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       bot.havocbot_role = havocbot_role_race;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+       {
+               if (!player.stored_netname)
+                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
+               if(player.stored_netname != player.netname)
+               {
+                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+                       strcpy(player.stored_netname, player.netname);
+               }
+       }
+
+       if (!IS_OBSERVER(player))
+       {
+               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+               {
+                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+                       speedaward_holder = player.netname;
+                       speedaward_uid = player.crypto_idfp;
+                       speedaward_lastupdate = time;
+               }
+               if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+               {
+                       string rr = RACE_RECORD;
+                       race_send_speedaward(MSG_ALL);
+                       speedaward_lastsent = speedaward_speed;
+                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+                       {
+                               speedaward_alltimebest = speedaward_speed;
+                               speedaward_alltimebest_holder = speedaward_holder;
+                               speedaward_alltimebest_uid = speedaward_uid;
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+                               race_send_speedaward_alltimebest(MSG_ALL);
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
+{
+       if(g_race_qualifying)
+               return true; // in qualifying, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(rc, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = race_teams;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
+{
+       // announce remaining frags if not in qualifying mode
+       if(!g_race_qualifying)
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetRecords)
+{
+       int record_page = M_ARGV(0, int);
+       string ret_string = M_ARGV(1, string);
+
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if(MapInfo_Get_ByID(i))
+               {
+                       float r = race_readTime(MapInfo_Map_bspname, 1);
+
+                       if(!r)
+                               continue;
+
+                       string h = race_readName(MapInfo_Map_bspname, 1);
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
+               }
+       }
+
+       M_ARGV(1, string) = ret_string;
+}
+
+MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
+{
+       return true; // doesn't work so well
+}
+
+MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
+{
+       entity player = M_ARGV(0, entity);
+
+       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
+{
+       float checkrules_timelimit = M_ARGV(1, float);
+       float checkrules_fraglimit = M_ARGV(2, float);
+
+       if(checkrules_timelimit >= 0)
+       {
+               if(!g_race_qualifying)
+               {
+                       M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
+                       return true;
+               }
+               else if(g_race_qualifying == 2)
+               {
+                       M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
+                       return true;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
+{
+       if(g_race_qualifying == 2)
+               warmup_stage = 0;
+}
+
+void race_Initialize()
+{
+       race_ScoreRules();
+       if(g_race_qualifying == 2)
+               warmup_stage = 0;
+}
+
+void rc_SetLimits()
+{
+       int fraglimit_override, leadlimit_override;
+       float timelimit_override, qualifying_override;
+
+       if(autocvar_g_race_teams)
+       {
+               GameRules_teams(true);
+               race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
+       }
+       else
+               race_teams = 0;
+
+       qualifying_override = autocvar_g_race_qualifying_timelimit_override;
+       fraglimit_override = autocvar_g_race_laps_limit;
+       leadlimit_override = 0; // currently not supported by race
+       timelimit_override = autocvar_timelimit_override;
+
+       float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
+
+       if(autocvar_g_campaign)
+       {
+               g_race_qualifying = 1;
+               independent_players = 1;
+       }
+       else if(want_qualifying)
+       {
+               g_race_qualifying = 2;
+               independent_players = 1;
+               race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
+               race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
+               race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
+               qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
+               fraglimit_override = 0;
+               leadlimit_override = 0;
+               timelimit_override = qualifying_override;
+       }
+       else
+               g_race_qualifying = 0;
+    GameRules_limit_score(fraglimit_override);
+    GameRules_limit_lead(leadlimit_override);
+    GameRules_limit_time(timelimit_override);
+    GameRules_limit_time_qualifying(qualifying_override);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/race/sv_race.qh b/qcsrc/common/gamemodes/gamemode/race/sv_race.qh
new file mode 100644 (file)
index 0000000..9928b8c
--- /dev/null
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+void rc_SetLimits();
+void race_Initialize();
+
+REGISTER_MUTATOR(rc, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               rc_SetLimits();
+
+               race_Initialize();
+       }
+       return 0;
+}
index ef7137a004e5147d7c002c3998dbcd3f80e90929..5c0e949a8ae1a6c6eaf3a795fbb1ab8a5a9ca6d5 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/tdm/tdm.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/tdm/sv_tdm.qc>
+#endif
index f1965c10954f1da4a2fd3e3ad9437bb2f21ad573..5be8ea6e6fa7b2537c6e7469d95785ee06f67857 100644 (file)
@@ -1,2 +1,4 @@
 // generated file; do not modify
-#include <common/gamemodes/gamemode/tdm/tdm.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/tdm/sv_tdm.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/sv_tdm.qc b/qcsrc/common/gamemodes/gamemode/tdm/sv_tdm.qc
new file mode 100644 (file)
index 0000000..43a9938
--- /dev/null
@@ -0,0 +1,63 @@
+#include "sv_tdm.qh"
+
+// TODO? rename to teamdeathmatch
+int autocvar_g_tdm_teams;
+int autocvar_g_tdm_teams_override;
+
+/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_tdm_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+spawnfunc(tdm_team)
+{
+       if(!g_tdm || !this.cnt) { delete(this); return; }
+
+       this.classname = "tdm_team";
+       this.team = this.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have team entities
+void tdm_SpawnTeam (string teamname, int teamcolor)
+{
+       entity this = new_pure(tdm_team);
+       this.netname = teamname;
+       this.cnt = teamcolor - 1;
+       this.team = teamcolor;
+       this.spawnfunc_checked = true;
+       //spawnfunc_tdm_team(this);
+}
+
+void tdm_DelayedInit(entity this)
+{
+       // if no teams are found, spawn defaults
+       if(find(NULL, classname, "tdm_team") == NULL)
+       {
+               LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
+
+               int numteams = autocvar_g_tdm_teams_override;
+               if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
+
+               int teams = BITS(bound(2, numteams, 4));
+               if(teams & BIT(0))
+                       tdm_SpawnTeam("Red", NUM_TEAM_1);
+               if(teams & BIT(1))
+                       tdm_SpawnTeam("Blue", NUM_TEAM_2);
+               if(teams & BIT(2))
+                       tdm_SpawnTeam("Yellow", NUM_TEAM_3);
+               if(teams & BIT(3))
+                       tdm_SpawnTeam("Pink", NUM_TEAM_4);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(tdm, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(1, string) = "tdm_team";
+}
+
+MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/sv_tdm.qh b/qcsrc/common/gamemodes/gamemode/tdm/sv_tdm.qh
new file mode 100644 (file)
index 0000000..adc6a3d
--- /dev/null
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+int autocvar_g_tdm_point_limit;
+int autocvar_g_tdm_point_leadlimit;
+bool autocvar_g_tdm_team_spawns;
+void tdm_DelayedInit(entity this);
+
+REGISTER_MUTATOR(tdm, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
+               GameRules_limit_score(autocvar_g_tdm_point_limit);
+        GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
+
+               InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
+       }
+       return 0;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc
deleted file mode 100644 (file)
index 39e5fec..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-#include "tdm.qh"
-
-// TODO: sv_tdm
-// TODO? rename to teamdeathmatch
-#ifdef SVQC
-int autocvar_g_tdm_teams;
-int autocvar_g_tdm_teams_override;
-
-/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map.
-Note: If you use spawnfunc_tdm_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
-Keys:
-"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
-"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
-spawnfunc(tdm_team)
-{
-       if(!g_tdm || !this.cnt) { delete(this); return; }
-
-       this.classname = "tdm_team";
-       this.team = this.cnt + 1;
-}
-
-// code from here on is just to support maps that don't have team entities
-void tdm_SpawnTeam (string teamname, int teamcolor)
-{
-       entity this = new_pure(tdm_team);
-       this.netname = teamname;
-       this.cnt = teamcolor - 1;
-       this.team = teamcolor;
-       this.spawnfunc_checked = true;
-       //spawnfunc_tdm_team(this);
-}
-
-void tdm_DelayedInit(entity this)
-{
-       // if no teams are found, spawn defaults
-       if(find(NULL, classname, "tdm_team") == NULL)
-       {
-               LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
-
-               int numteams = autocvar_g_tdm_teams_override;
-               if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
-
-               int teams = BITS(bound(2, numteams, 4));
-               if(teams & BIT(0))
-                       tdm_SpawnTeam("Red", NUM_TEAM_1);
-               if(teams & BIT(1))
-                       tdm_SpawnTeam("Blue", NUM_TEAM_2);
-               if(teams & BIT(2))
-                       tdm_SpawnTeam("Yellow", NUM_TEAM_3);
-               if(teams & BIT(3))
-                       tdm_SpawnTeam("Pink", NUM_TEAM_4);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(1, string) = "tdm_team";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-#endif
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh
deleted file mode 100644 (file)
index 1c8674a..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-#include <common/mutators/base.qh>
-int autocvar_g_tdm_point_limit;
-int autocvar_g_tdm_point_leadlimit;
-bool autocvar_g_tdm_team_spawns;
-void tdm_DelayedInit(entity this);
-
-REGISTER_MUTATOR(tdm, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
-               GameRules_limit_score(autocvar_g_tdm_point_limit);
-        GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
-
-               InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
-       }
-       return 0;
-}
-#endif
index 979477cbafab6c676c4fdc7cabfeedf43134725b..f8950684b4967eabaa9990de93a7e53746d8f0cc 100644 (file)
@@ -1,8 +1,7 @@
 #pragma once
 
 // TODO: find a better location for these?
-float total_players;
-float redalive, bluealive, yellowalive, pinkalive;
+int total_players;
 
 // todo: accept the number of teams as a parameter
 void GameRules_teams(bool value);
index 45a8f1323b9f142bd02b5eae8ad61202a273ae63..8bd0c41dab29ec33e9f4f2877f00638e9578eb45 100644 (file)
@@ -186,20 +186,6 @@ LEGACY_IMPULSE(g_waypointsprite_clear_personal, 47, "waypoint_clear_personal")
 REGISTER_IMPULSE(waypoint_clear, 48)
 LEGACY_IMPULSE(g_waypointsprite_clear, 48, "waypoint_clear")
 
-REGISTER_IMPULSE(navwaypoint_spawn, 103)
-LEGACY_IMPULSE(g_waypointeditor_spawn, 103, "navwaypoint_spawn")
-
-REGISTER_IMPULSE(navwaypoint_remove, 104)
-LEGACY_IMPULSE(g_waypointeditor_remove, 104, "navwaypoint_remove")
-
-REGISTER_IMPULSE(navwaypoint_relink, 105)
-LEGACY_IMPULSE(g_waypointeditor_relinkall, 105, "navwaypoint_relink")
-
-REGISTER_IMPULSE(navwaypoint_save, 106)
-LEGACY_IMPULSE(g_waypointeditor_saveall, 106, "navwaypoint_save")
-
-REGISTER_IMPULSE(navwaypoint_unreachable, 107)
-LEGACY_IMPULSE(g_waypointeditor_unreachable, 107, "navwaypoint_unreachable")
 
 #define CHIMPULSE(id, n) _CHIMPULSE(CHIMPULSE_##id, n)
 #define _CHIMPULSE(id, n) \
index 9075c0912ed2a5044ba8e7b80b299a05723f31ba..ba824f40b41c14c470a214c33eeb0bd6e9064c4d 100644 (file)
@@ -107,7 +107,7 @@ bool Inventory_Send(Inventory this, Client to, int sf)
     TC(Inventory, this);
     WriteHeader(MSG_ENTITY, ENT_CLIENT_INVENTORY);
     entity e = this.owner;
-    if (IS_SPEC(e)) e = e.enemy;
+    if (IS_SPEC(e)) e = PS(e.enemy); // TODO: how can this *ever* be the case?
     TC(Player, e);
     Inventory data = e.inventory;
     Inventory_Write(data);
@@ -118,7 +118,7 @@ void Inventory_new(entity e)
 {
     Inventory inv = NEW(Inventory), bak = NEW(Inventory);
     inv.inventory = bak;
-    inv.drawonlytoclient = e;
+    inv.drawonlytoclient = IS_CLIENT(e) ? e : e.m_client;
     Net_LinkEntity((inv.owner = e).inventory = inv, false, 0, Inventory_Send);
 }
 void Inventory_delete(entity e) { delete(e.inventory.inventory); delete(e.inventory); }
index 3109e7c92f93c66adc8c0f5d274bef901bf019e3..030b4db1c089f53f06c62a1cb19590ac48b50977 100644 (file)
@@ -51,16 +51,26 @@ const int IT_PICKUPMASK                     = IT_UNLIMITED_AMMO | IT_JETPACK | IT_FU
 .float  strength_finished = _STAT(STRENGTH_FINISHED);
 .float  invincible_finished = _STAT(INVINCIBLE_FINISHED);
 
+#define spawnfunc_body(item) \
+       if (!Item_IsDefinitionAllowed(item)) \
+       { \
+               startitem_failed = true; \
+               delete(this); \
+               return; \
+       } \
+       StartItem(this, item)
+
 #define SPAWNFUNC_ITEM(name, item) \
-    spawnfunc(name) \
+       spawnfunc(name) \
+       { \
+               spawnfunc_body(item); \
+       }
+
+#define SPAWNFUNC_ITEM_COND(name, cond, item1, item2) \
+       spawnfunc(name) \
        { \
-               if (!Item_IsDefinitionAllowed(item)) \
-               { \
-                       startitem_failed = true; \
-                       delete(this); \
-                       return; \
-               } \
-               StartItem(this, item); \
+               entity item = (cond) ? item1 : item2; \
+               spawnfunc_body(item); \
        }
 
 #else
@@ -73,7 +83,7 @@ enum
 {
        ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay.
        ITEM_FLAG_MUTATORBLOCKED = BIT(1),
-    ITEM_FLAG_RESOURCE = BIT(2) ///< Item is is a resource, not a held item.
+       ITEM_FLAG_RESOURCE = BIT(2) ///< Item is is a resource, not a held item.
 };
 
 #define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__)
index 4c37464ad83c8de28d6a6f706d8f264ff26727f9..3249f07bca6caf6be0d76ae1b49ad4542c255c4c 100644 (file)
@@ -63,7 +63,7 @@ REGISTER_ITEM(Bullets, Bullets) {
     this.m_model    =   MDL_Bullets_ITEM;
 #endif
     this.netname    =   "bullets";
-    this.m_name     =   "bullets";
+    this.m_name     =   _("bullets");
     this.m_icon     =   "ammo_bullets";
 #ifdef SVQC
     this.m_botvalue =   1500;
@@ -93,7 +93,7 @@ REGISTER_ITEM(Cells, Ammo) {
     this.m_model    =   MDL_Cells_ITEM;
 #endif
     this.netname    =   "cells";
-    this.m_name     =   "cells";
+    this.m_name     =   _("cells");
     this.m_icon     =   "ammo_cells";
 #ifdef SVQC
     this.m_botvalue =   1500;
@@ -123,7 +123,7 @@ REGISTER_ITEM(Plasma, Ammo) {
     this.m_model    =   MDL_Plasma_ITEM;
 #endif
     this.netname    =   "plasma";
-    this.m_name     =   "plasma";
+    this.m_name     =   _("plasma");
     this.m_icon     =   "ammo_plasma";
 #ifdef SVQC
     this.m_botvalue =   1500;
@@ -153,7 +153,7 @@ REGISTER_ITEM(Rockets, Ammo) {
     this.m_model    =   MDL_Rockets_ITEM;
 #endif
     this.netname    =   "rockets";
-    this.m_name     =   "rockets";
+    this.m_name     =   _("rockets");
     this.m_icon     =   "ammo_rockets";
 #ifdef SVQC
     this.m_botvalue =   1500;
@@ -187,7 +187,7 @@ REGISTER_ITEM(Shells, Shells) {
     this.m_model    =   MDL_Shells_ITEM;
 #endif
     this.netname    =   "shells";
-    this.m_name     =   "shells";
+    this.m_name     =   _("shells");
     this.m_icon     =   "ammo_shells";
 #ifdef SVQC
     this.m_botvalue =   1000;
index ee39aa59242111771347d38005fe0d3c89113e4c..2ecd8355714c950f1089cc7d03bbcbc1c8a2850a 100644 (file)
@@ -39,7 +39,7 @@ REGISTER_ITEM(ArmorSmall, Armor) {
     this.m_sound                =   SND_ArmorSmall;
 #endif
     this.netname                =   "armor_small";
-    this.m_name                 =   "5 Armor";
+    this.m_name                 =   _("Small armor");
     this.m_icon                 =   "armor";
 #ifdef SVQC
     this.m_itemid               =   IT_ARMOR_SHARD;
@@ -77,7 +77,7 @@ REGISTER_ITEM(ArmorMedium, Armor) {
     this.m_sound                =   SND_ArmorMedium;
 #endif
     this.netname                =   "armor_medium";
-    this.m_name                 =   "25 Armor";
+    this.m_name                 =   _("Medium armor");
     this.m_icon                 =   "armor";
 #ifdef SVQC
     this.m_itemid               =   IT_ARMOR;
@@ -115,7 +115,7 @@ REGISTER_ITEM(ArmorBig, Armor) {
     this.m_sound                =   SND_ArmorBig;
 #endif
     this.netname                =   "armor_big";
-    this.m_name                 =   "50 Armor";
+    this.m_name                 =   _("Big armor");
     this.m_icon                 =   "armor";
     this.m_color                =   '0 1 0';
     this.m_waypoint             =   _("Big armor");
@@ -155,7 +155,7 @@ REGISTER_ITEM(ArmorMega, Armor) {
     this.m_sound                =   SND_ArmorMega;
 #endif
     this.netname                =   "armor_mega";
-    this.m_name                 =   "100 Armor";
+    this.m_name                 =   _("Mega armor");
     this.m_icon                 =   "item_large_armor";
     this.m_color                =   '0 1 0';
     this.m_waypoint             =   _("Mega armor");
index bf515fe4dd7f7c167d31c7f85b6eb3bc9e409fb3..e6a9dd9883589f13776384d823e207beae250357 100644 (file)
@@ -39,7 +39,7 @@ REGISTER_ITEM(HealthSmall, Health) {
     this.m_sound                =   SND_HealthSmall;
 #endif
     this.netname                =   "health_small";
-    this.m_name                 =   "5 Health";
+    this.m_name                 =   _("Small health");
     this.m_icon                 =   "health";
 #ifdef SVQC
     this.m_itemid               =   IT_5HP;
@@ -77,7 +77,7 @@ REGISTER_ITEM(HealthMedium, Health) {
     this.m_sound                =   SND_HealthMedium;
 #endif
     this.netname                =   "health_medium";
-    this.m_name                 =   "25 Health";
+    this.m_name                 =   _("Medium health");
     this.m_icon                 =   "health";
 #ifdef SVQC
     this.m_itemid               =   IT_25HP;
@@ -115,7 +115,7 @@ REGISTER_ITEM(HealthBig, Health) {
     this.m_sound                =   SND_HealthBig;
 #endif
     this.netname                =   "health_big";
-    this.m_name                 =   "50 Health";
+    this.m_name                 =   _("Big health");
     this.m_icon                 =   "health";
     this.m_color                =   '1 0 0';
     this.m_waypoint             =   _("Big health");
@@ -155,7 +155,7 @@ REGISTER_ITEM(HealthMega, Health) {
     this.m_sound                =   SND_HealthMega;
 #endif
     this.netname                =   "health_mega";
-    this.m_name                 =   "100 Health";
+    this.m_name                 =   _("Mega health");
     this.m_icon                 =   "item_mega_health";
     this.m_color                =   '1 0 0';
     this.m_waypoint             =   _("Mega health");
index 760033861a7db8377342bc6ff7dbd6bef1901b4b..24d040d56b403753c50f12f4ddcee7869a7f9cff 100644 (file)
@@ -35,7 +35,7 @@ REGISTER_ITEM(Jetpack, Powerup) {
     this.m_itemid               =   IT_JETPACK;
 #endif
     this.netname                =   "jetpack";
-    this.m_name                 =   "Jetpack";
+    this.m_name                 =   _("Jetpack");
     this.m_icon                 =   "jetpack";
     this.m_color                =   '0.5 0.5 0.5';
     this.m_waypoint             =   _("Jetpack");
@@ -68,7 +68,7 @@ REGISTER_ITEM(JetpackFuel, Ammo) {
     this.m_model    =   MDL_JetpackFuel_ITEM;
 #endif
     this.netname    =   "fuel";
-    this.m_name     =   "Fuel";
+    this.m_name     =   _("fuel");
     this.m_icon     =   "ammo_fuel";
 #ifdef SVQC
     this.m_botvalue =   2000;
@@ -93,7 +93,7 @@ REGISTER_ITEM(JetpackRegen, JetpackRegen) {
     this.m_model                =   MDL_JetpackRegen_ITEM;
 #endif
     this.netname                =   "fuel_regen";
-    this.m_name                 =   "Fuel regenerator";
+    this.m_name                 =   _("Fuel regenerator");
     this.m_icon                 =   "fuelregen";
     this.m_color                =   '1 0.5 0';
     this.m_waypoint             =   _("Fuel regen");
index b5944fc0a3def7d7235f09bfc66e6ad33f2fb11e..a3c2d779edcde089ac3d4f7e142693de8e47070b 100644 (file)
@@ -11,9 +11,10 @@ METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player))
     TC(Pickup, this);
     bool b = Item_GiveTo(item, player);
     if (b) {
-        LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
-        player.inventory.inv_items[this.m_id]++;
-        Inventory_update(player);
+        //LOG_DEBUGF("entity %i picked up %s", player, this.m_name);
+        entity store = IS_PLAYER(player) ? PS(player) : player;
+        store.inventory.inv_items[this.m_id]++;
+        Inventory_update(store);
     }
     return b;
 }
index 1c10afa488a581065888af5fa7a7324e82ca2eb6..43414b3d06f68af39afcb97f5eb0ebb7f2cf43ee 100644 (file)
@@ -10,7 +10,7 @@ CLASS(Powerup, Pickup)
 #ifdef SVQC
     ATTRIB(Powerup, m_mins, vector, '-16 -16 0');
     ATTRIB(Powerup, m_maxs, vector, '16 16 80');
-    ATTRIB(Powerup, m_botvalue, int, 20000);
+    ATTRIB(Powerup, m_botvalue, int, 11000);
     ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
     ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
     ATTRIB(Powerup, m_respawntimejitter, float(), GET(g_pickup_respawntimejitter_powerup));
@@ -40,7 +40,7 @@ REGISTER_ITEM(Strength, Powerup) {
     this.m_respawnsound     =   SND_STRENGTH_RESPAWN;
 #endif
     this.netname            =   "strength";
-    this.m_name             =   "Strength Powerup";
+    this.m_name             =   _("Strength");
     this.m_icon             =   "strength";
     this.m_color            =   '0 0 1';
     this.m_waypoint         =   _("Strength");
@@ -76,7 +76,7 @@ REGISTER_ITEM(Shield, Powerup) {
     this.m_respawnsound     =   SND_SHIELD_RESPAWN;
 #endif
     this.netname            =   "invincible";
-    this.m_name             =   "Shield";
+    this.m_name             =   _("Shield");
     this.m_icon             =   "shield";
     this.m_color            =   '1 0 1';
     this.m_waypoint         =   _("Shield");
index 0884bc8d79054c29d6d88bacb75ad88de011e09b..5cfc8785140bfca8dec3ae75cfd4d079293c5972 100644 (file)
@@ -424,27 +424,7 @@ void _MapInfo_Map_Reset()
 
 string _MapInfo_GetDefault(Gametype t)
 {
-       switch(t)
-       {
-               case MAPINFO_TYPE_DEATHMATCH:      return "30 20 0";
-               case MAPINFO_TYPE_TEAM_DEATHMATCH: return "50 20 2 0";
-               case MAPINFO_TYPE_DOMINATION:      return "200 20 0";
-               case MAPINFO_TYPE_CTF:             return "300 20 10 0";
-               case MAPINFO_TYPE_LMS:             return "9 20 0";
-               case MAPINFO_TYPE_CA:              return "10 20 0";
-               case MAPINFO_TYPE_KEYHUNT:         return "1000 20 3 0";
-               case MAPINFO_TYPE_ASSAULT:         return "20 0";
-               case MAPINFO_TYPE_RACE:            return "20 5 7 15 0";
-               case MAPINFO_TYPE_ONSLAUGHT:       return "20 0";
-               case MAPINFO_TYPE_NEXBALL:         return "5 20 0";
-               case MAPINFO_TYPE_CTS:             return "20 0 0";
-               case MAPINFO_TYPE_FREEZETAG:       return "10 20 0";
-               // NOTE: DO NOT ADD ANY MORE GAME TYPES HERE
-               // THIS IS JUST LEGACY SUPPORT FOR NEXUIZ MAPS
-               // ONLY ADD NEW STUFF TO _MapInfo_GetDefaultEx
-               // THIS FUNCTION WILL EVENTUALLY BE REMOVED
-               default:                           return "";
-       }
+       return t.m_legacydefaults;
 }
 
 void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisType, int load_default)
@@ -457,7 +437,7 @@ void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisTy
        if(load_default)
                _MapInfo_Map_ApplyGametype(_MapInfo_GetDefault(pThisType), pWantedType, pThisType, false);
 
-       if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_RACE || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit
+       if(!pWantedType.frags) // these modes don't use fraglimit
        {
                cvar_set("fraglimit", "0");
        }
@@ -485,6 +465,8 @@ void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisTy
        // rc = timelimit timelimit_qualification laps laps_teamplay
        if(pWantedType == MAPINFO_TYPE_RACE)
        {
+               cvar_set("fraglimit", "0"); // special case!
+
                sa = car(s); if(sa == "") sa = cvar_string("timelimit");
                cvar_set("g_race_qualifying_timelimit", sa);
                s = cdr(s);
@@ -502,7 +484,7 @@ void _MapInfo_Map_ApplyGametype(string s, Gametype pWantedType, Gametype pThisTy
                s = cdr(s);
        }
 
-       if(pWantedType == MAPINFO_TYPE_ASSAULT || pWantedType == MAPINFO_TYPE_ONSLAUGHT || pWantedType == MAPINFO_TYPE_CTS) // these modes don't use fraglimit
+       if(!pWantedType.frags) // these modes don't use fraglimit
        {
                cvar_set("leadlimit", "0");
        }
@@ -854,6 +836,9 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
                        if(fexists(strcat("scripts/", pFilename, ".arena")))
                                fputs(fh, "settemp_for_type all sv_q3acompat_machineshotgunswap 1\n");
 
+                       if(fexists(strcat("scripts/", pFilename, ".defi")))
+                               fputs(fh, "settemp_for_type all sv_vq3compat 1\n");
+
                        fputs(fh, "// optional: fog density red green blue alpha mindist maxdist\n");
                        fputs(fh, "// optional: settemp_for_type (all|gametypename) cvarname value\n");
                        fputs(fh, "// optional: clientsettemp_for_type (all|gametypename) cvarname value\n");
@@ -1064,13 +1049,7 @@ int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametyp
 {
        int r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
 
-       if(cvar("g_tdm_on_dm_maps"))
-       {
-               // if this is set, all DM maps support TDM too
-               if (!(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags))
-                       if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags)
-                               _MapInfo_Map_ApplyGametypeEx ("", pGametypeToSet, MAPINFO_TYPE_TEAM_DEATHMATCH);
-       }
+       FOREACH(Gametypes, it.m_isForcedSupported(it), _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, it));
 
        if(pGametypeToSet)
        {
index 4addd24001f6d32a594424a2761855c222f3b300..6fbb7ce896235d53fe240be81fd83ea0aea25002 100644 (file)
@@ -31,6 +31,8 @@ CLASS(Gametype, Object)
     ATTRIB(Gametype, message, string);
     /** does this gametype support teamplay? */
     ATTRIB(Gametype, team, bool, false);
+    /** does this gametype use a point limit? */
+    ATTRIB(Gametype, frags, bool, true);
     /** game type defaults */
     ATTRIB(Gametype, model2, string);
     /** game type description */
@@ -40,6 +42,9 @@ CLASS(Gametype, Object)
     ATTRIB(Gametype, m_modicons_reset, void());
 #endif
 
+    /** DO NOT USE, this is compatibility for legacy maps! */
+    ATTRIB(Gametype, m_legacydefaults, string, "");
+
     ATTRIB(Gametype, m_mutators, string);
     METHOD(Gametype, m_parse_mapinfo, bool(string k, string v))
     {
@@ -57,6 +62,15 @@ CLASS(Gametype, Object)
     {
         return false;
     }
+    METHOD(Gametype, m_isForcedSupported, bool(Gametype this))
+    {
+        return false;
+    }
+    METHOD(Gametype, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        string_null,                    _("The amount of frags needed before the match will end"));
+    }
 
     METHOD(Gametype, describe, string(Gametype this))
     {
@@ -70,7 +84,7 @@ CLASS(Gametype, Object)
         returns(this.message, strcat("gametype_", this.mdl));
     }
 
-    METHOD(Gametype, gametype_init, void(Gametype this, string hname, string sname, string g_name, bool gteamplay, string mutators, string defaults, string gdescription))
+    METHOD(Gametype, gametype_init, void(Gametype this, string hname, string sname, string g_name, bool gteamplay, bool gusepoints, string mutators, string defaults, string gdescription))
     {
         this.netname = g_name;
         this.mdl = sname;
@@ -79,6 +93,7 @@ CLASS(Gametype, Object)
         this.m_mutators = cons(sname, mutators);
         this.model2 = defaults;
         this.gametype_description = gdescription;
+        this.frags = gusepoints;
 
         // same as `1 << m_id`
         MAPINFO_TYPE_ALL |= this.items = this.m_flags = (MAPINFO_TYPE_ALL + 1);
@@ -96,24 +111,31 @@ REGISTRY_CHECK(Gametypes)
 CLASS(Deathmatch, Gametype)
     INIT(Deathmatch)
     {
-        this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,"","timelimit=15 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
+        this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,true,"","timelimit=15 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
     }
     METHOD(Deathmatch, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
     {
         return true;
     }
+    ATTRIB(Deathmatch, m_legacydefaults, string, "30 20 0");
 ENDCLASS(Deathmatch)
 REGISTER_GAMETYPE(DEATHMATCH, NEW(Deathmatch));
 
 CLASS(LastManStanding, Gametype)
     INIT(LastManStanding)
     {
-        this.gametype_init(this, _("Last Man Standing"),"lms","g_lms",false,"","timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
+        this.gametype_init(this, _("Last Man Standing"),"lms","g_lms",false,true,"","timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
     }
     METHOD(LastManStanding, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
     {
         return true;
     }
+    METHOD(LastManStanding, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Lives:"),           3,   50,  1, "g_lms_lives_override",      string_null,                    string_null);
+    }
+    ATTRIB(LastManStanding, m_legacydefaults, string, "9 20 0");
 ENDCLASS(LastManStanding)
 REGISTER_GAMETYPE(LMS, NEW(LastManStanding));
 
@@ -123,7 +145,7 @@ void HUD_Mod_Race(vector pos, vector mySize);
 CLASS(Race, Gametype)
     INIT(Race)
     {
-        this.gametype_init(this, _("Race"),"rc","g_race",false,"","timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line"));
+        this.gametype_init(this, _("Race"),"rc","g_race",false,true,"","timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line"));
     }
     METHOD(Race, m_parse_mapinfo, bool(string k, string v))
     {
@@ -147,9 +169,15 @@ CLASS(Race, Gametype)
     {
         return true;
     }
+    METHOD(Race, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Laps:"),            1,   25,  1, "g_race_laps_limit",         string_null,                    string_null);
+    }
 #ifdef CSQC
     ATTRIB(Race, m_modicons, void(vector pos, vector mySize), HUD_Mod_Race);
 #endif
+    ATTRIB(Race, m_legacydefaults, string, "20 5 7 15 0");
 ENDCLASS(Race)
 REGISTER_GAMETYPE(RACE, NEW(Race));
 #define g_race IS_GAMETYPE(RACE)
@@ -157,7 +185,7 @@ REGISTER_GAMETYPE(RACE, NEW(Race));
 CLASS(RaceCTS, Gametype)
     INIT(RaceCTS)
     {
-        this.gametype_init(this, _("Race CTS"),"cts","g_cts",false,"cloaked","timelimit=20",_("Race for fastest time."));
+        this.gametype_init(this, _("Race CTS"),"cts","g_cts",false,false,"cloaked","timelimit=20",_("Race for fastest time."));
     }
     METHOD(RaceCTS, m_generate_mapinfo, void(Gametype this, string v))
     {
@@ -171,9 +199,15 @@ CLASS(RaceCTS, Gametype)
         // for map databases
         //  cvar_set("fraglimit", sa);
     }
+    METHOD(RaceCTS, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null);
+    }
 #ifdef CSQC
     ATTRIB(RaceCTS, m_modicons, void(vector pos, vector mySize), HUD_Mod_Race);
 #endif
+    ATTRIB(RaceCTS, m_legacydefaults, string, "20 0 0");
 ENDCLASS(RaceCTS)
 REGISTER_GAMETYPE(CTS, NEW(RaceCTS));
 #define g_cts IS_GAMETYPE(CTS)
@@ -181,7 +215,7 @@ REGISTER_GAMETYPE(CTS, NEW(RaceCTS));
 CLASS(TeamDeathmatch, Gametype)
     INIT(TeamDeathmatch)
     {
-        this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,"","timelimit=15 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
+        this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,true,"","timelimit=15 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
     }
     METHOD(TeamDeathmatch, m_parse_mapinfo, bool(string k, string v))
     {
@@ -202,10 +236,26 @@ CLASS(TeamDeathmatch, Gametype)
             return true;
         return false;
     }
+    METHOD(TeamDeathmatch, m_isForcedSupported, bool(Gametype this))
+    {
+        if(cvar("g_tdm_on_dm_maps"))
+        {
+            // if this is set, all DM maps support TDM too
+            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+                return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported)
+        }
+        return false;
+    }
     METHOD(TeamDeathmatch, m_setTeams, void(string sa))
     {
         cvar_set("g_tdm_teams", sa);
     }
+    METHOD(TeamDeathmatch, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),     5,  100,  5, "g_tdm_point_limit",         "g_tdm_teams_override",         _("The amount of points needed before the match will end"));
+    }
+    ATTRIB(TeamDeathmatch, m_legacydefaults, string, "50 20 2 0");
 ENDCLASS(TeamDeathmatch)
 REGISTER_GAMETYPE(TEAM_DEATHMATCH, NEW(TeamDeathmatch));
 #define g_tdm IS_GAMETYPE(TEAM_DEATHMATCH)
@@ -217,7 +267,7 @@ void HUD_Mod_CTF_Reset();
 CLASS(CaptureTheFlag, Gametype)
     INIT(CaptureTheFlag)
     {
-        this.gametype_init(this, _("Capture the Flag"),"ctf","g_ctf",true,"","timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it, defend your base from the other team"));
+        this.gametype_init(this, _("Capture the Flag"),"ctf","g_ctf",true,true,"","timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it, defend your base from the other team"));
     }
     METHOD(CaptureTheFlag, m_generate_mapinfo, void(Gametype this, string v))
     {
@@ -232,10 +282,16 @@ CLASS(CaptureTheFlag, Gametype)
     {
         cvar_set("fraglimit", sa);
     }
+    METHOD(CaptureTheFlag, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Capture limit:"),   1,   20,  1, "capturelimit_override",     string_null,                    _("The amount of captures needed before the match will end"));
+    }
 #ifdef CSQC
     ATTRIB(CaptureTheFlag, m_modicons, void(vector pos, vector mySize), HUD_Mod_CTF);
     ATTRIB(CaptureTheFlag, m_modicons_reset, void(), HUD_Mod_CTF_Reset);
 #endif
+    ATTRIB(CaptureTheFlag, m_legacydefaults, string, "300 20 10 0");
 ENDCLASS(CaptureTheFlag)
 REGISTER_GAMETYPE(CTF, NEW(CaptureTheFlag));
 #define g_ctf IS_GAMETYPE(CTF)
@@ -246,7 +302,7 @@ void HUD_Mod_CA(vector pos, vector mySize);
 CLASS(ClanArena, Gametype)
     INIT(ClanArena)
     {
-        this.gametype_init(this, _("Clan Arena"),"ca","g_ca",true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"));
+        this.gametype_init(this, _("Clan Arena"),"ca","g_ca",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"));
     }
     METHOD(ClanArena, m_parse_mapinfo, bool(string k, string v))
     {
@@ -271,9 +327,15 @@ CLASS(ClanArena, Gametype)
     {
         cvar_set("g_ca_teams", sa);
     }
+    METHOD(ClanArena, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_ca_teams_override",          _("The amount of frags needed before the match will end"));
+    }
 #ifdef CSQC
     ATTRIB(ClanArena, m_modicons, void(vector pos, vector mySize), HUD_Mod_CA);
 #endif
+    ATTRIB(ClanArena, m_legacydefaults, string, "10 20 0");
 ENDCLASS(ClanArena)
 REGISTER_GAMETYPE(CA, NEW(ClanArena));
 #define g_ca IS_GAMETYPE(CA)
@@ -284,7 +346,7 @@ void HUD_Mod_Dom(vector pos, vector mySize);
 CLASS(Domination, Gametype)
     INIT(Domination)
     {
-        this.gametype_init(this, _("Domination"),"dom","g_domination",true,"","timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture and defend all the control points to win"));
+        this.gametype_init(this, _("Domination"),"dom","g_domination",true,true,"","timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture and defend all the control points to win"));
     }
     METHOD(Domination, m_parse_mapinfo, bool(string k, string v))
     {
@@ -304,9 +366,15 @@ CLASS(Domination, Gametype)
         if(v == "dom_controlpoint")
             MapInfo_Map_supportedGametypes |= this.m_flags;
     }
+    METHOD(Domination, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),    50,  500, 10, "g_domination_point_limit",  "g_domination_teams_override",  _("The amount of points needed before the match will end"));
+    }
 #ifdef CSQC
     ATTRIB(Domination, m_modicons, void(vector pos, vector mySize), HUD_Mod_Dom);
 #endif
+    ATTRIB(Domination, m_legacydefaults, string, "200 20 0");
 ENDCLASS(Domination)
 REGISTER_GAMETYPE(DOMINATION, NEW(Domination));
 
@@ -316,7 +384,7 @@ void HUD_Mod_KH(vector pos, vector mySize);
 CLASS(KeyHunt, Gametype)
     INIT(KeyHunt)
     {
-        this.gametype_init(this, _("Key Hunt"),"kh","g_keyhunt",true,"","timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round"));
+        this.gametype_init(this, _("Key Hunt"),"kh","g_keyhunt",true,true,"","timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round"));
     }
     METHOD(KeyHunt, m_parse_mapinfo, bool(string k, string v))
     {
@@ -341,16 +409,22 @@ CLASS(KeyHunt, Gametype)
     {
         cvar_set("g_keyhunt_teams", sa);
     }
+    METHOD(KeyHunt, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),   200, 1500, 50, "g_keyhunt_point_limit",     "g_keyhunt_teams_override",     _("The amount of points needed before the match will end"));
+    }
 #ifdef CSQC
     ATTRIB(KeyHunt, m_modicons, void(vector pos, vector mySize), HUD_Mod_KH);
 #endif
+    ATTRIB(KeyHunt, m_legacydefaults, string, "1000 20 3 0");
 ENDCLASS(KeyHunt)
 REGISTER_GAMETYPE(KEYHUNT, NEW(KeyHunt));
 
 CLASS(Assault, Gametype)
     INIT(Assault)
     {
-        this.gametype_init(this, _("Assault"),"as","g_assault",true,"","timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out"));
+        this.gametype_init(this, _("Assault"),"as","g_assault",true,false,"","timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out"));
     }
     METHOD(Assault, m_generate_mapinfo, void(Gametype this, string v))
     {
@@ -361,6 +435,12 @@ CLASS(Assault, Gametype)
     {
         return true;
     }
+    METHOD(Assault, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null);
+    }
+    ATTRIB(Assault, m_legacydefaults, string, "20 0");
 ENDCLASS(Assault)
 REGISTER_GAMETYPE(ASSAULT, NEW(Assault));
 #define g_assault IS_GAMETYPE(ASSAULT)
@@ -368,13 +448,19 @@ REGISTER_GAMETYPE(ASSAULT, NEW(Assault));
 CLASS(Onslaught, Gametype)
     INIT(Onslaught)
     {
-        this.gametype_init(this, _("Onslaught"),"ons","g_onslaught",true,"","pointlimit=1 timelimit=20",_("Capture control points to reach and destroy the enemy generator"));
+        this.gametype_init(this, _("Onslaught"),"ons","g_onslaught",true,false,"","pointlimit=1 timelimit=20",_("Capture control points to reach and destroy the enemy generator"));
     }
     METHOD(Onslaught, m_generate_mapinfo, void(Gametype this, string v))
     {
         if(v == "onslaught_generator")
             MapInfo_Map_supportedGametypes |= this.m_flags;
     }
+    METHOD(Onslaught, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null);
+    }
+    ATTRIB(Onslaught, m_legacydefaults, string, "20 0");
 ENDCLASS(Onslaught)
 REGISTER_GAMETYPE(ONSLAUGHT, NEW(Onslaught));
 
@@ -384,7 +470,7 @@ void HUD_Mod_NexBall(vector pos, vector mySize);
 CLASS(NexBall, Gametype)
     INIT(NexBall)
     {
-        this.gametype_init(this, _("Nexball"),"nb","g_nexball",true,"","timelimit=20 pointlimit=5 leadlimit=0",_("Shoot and kick the ball into the enemies goal, keep your goal clean"));
+        this.gametype_init(this, _("Nexball"),"nb","g_nexball",true,true,"","timelimit=20 pointlimit=5 leadlimit=0",_("Shoot and kick the ball into the enemies goal, keep your goal clean"));
     }
     METHOD(NexBall, m_generate_mapinfo, void(Gametype this, string v))
     {
@@ -395,9 +481,15 @@ CLASS(NexBall, Gametype)
     {
         return true;
     }
+    METHOD(NexBall, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Goals:"),           1,   50,  1, "g_nexball_goallimit",       string_null,                    _("The amount of goals needed before the match will end"));
+    }
 #ifdef CSQC
     ATTRIB(NexBall, m_modicons, void(vector pos, vector mySize), HUD_Mod_NexBall);
 #endif
+    ATTRIB(NexBall, m_legacydefaults, string, "5 20 0");
 ENDCLASS(NexBall)
 REGISTER_GAMETYPE(NEXBALL, NEW(NexBall));
 #define g_nexball IS_GAMETYPE(NEXBALL)
@@ -405,7 +497,7 @@ REGISTER_GAMETYPE(NEXBALL, NEW(NexBall));
 CLASS(FreezeTag, Gametype)
     INIT(FreezeTag)
     {
-        this.gametype_init(this, _("Freeze Tag"),"ft","g_freezetag",true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to frozen teammates to revive them; freeze all enemies to win"));
+        this.gametype_init(this, _("Freeze Tag"),"ft","g_freezetag",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to frozen teammates to revive them; freeze all enemies to win"));
     }
     METHOD(FreezeTag, m_parse_mapinfo, bool(string k, string v))
     {
@@ -430,9 +522,15 @@ CLASS(FreezeTag, Gametype)
     {
         cvar_set("g_freezetag_teams", sa);
     }
+    METHOD(FreezeTag, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_freezetag_teams_override",   _("The amount of frags needed before the match will end"));
+    }
 #ifdef CSQC
     ATTRIB(FreezeTag, m_modicons, void(vector pos, vector mySize), HUD_Mod_CA);
 #endif
+    ATTRIB(FreezeTag, m_legacydefaults, string, "10 20 0");
 ENDCLASS(FreezeTag)
 REGISTER_GAMETYPE(FREEZETAG, NEW(FreezeTag));
 #define g_freezetag IS_GAMETYPE(FREEZETAG)
@@ -443,7 +541,7 @@ void HUD_Mod_Keepaway(vector pos, vector mySize);
 CLASS(Keepaway, Gametype)
     INIT(Keepaway)
     {
-        this.gametype_init(this, _("Keepaway"),"ka","g_keepaway",false,"","timelimit=20 pointlimit=30",_("Hold the ball to get points for kills"));
+        this.gametype_init(this, _("Keepaway"),"ka","g_keepaway",false,true,"","timelimit=20 pointlimit=30",_("Hold the ball to get points for kills"));
     }
     METHOD(Keepaway, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
     {
@@ -458,7 +556,7 @@ REGISTER_GAMETYPE(KEEPAWAY, NEW(Keepaway));
 CLASS(Invasion, Gametype)
     INIT(Invasion)
     {
-        this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,"","pointlimit=50 teams=0 type=0",_("Survive against waves of monsters"));
+        this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,true,"","pointlimit=50 teams=0 type=0",_("Survive against waves of monsters"));
     }
     METHOD(Invasion, m_parse_mapinfo, bool(string k, string v))
     {
@@ -477,9 +575,35 @@ CLASS(Invasion, Gametype)
         if(v == "invasion_spawnpoint")
             MapInfo_Map_supportedGametypes |= this.m_flags;
     }
+    METHOD(Invasion, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns))
+    {
+        TC(Gametype, this);
+        returns(menu, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null);
+    }
 ENDCLASS(Invasion)
 REGISTER_GAMETYPE(INVASION, NEW(Invasion));
 
+CLASS(Duel, Gametype)
+    INIT(Duel)
+    {
+        this.gametype_init(this, _("Duel"),"duel","g_duel",false,true,"","timelimit=10 pointlimit=0 leadlimit=0",_("Fight in a one versus one arena battle to decide the winner"));
+    }
+    METHOD(Duel, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        return (diameter < 16384);
+    }
+    METHOD(Duel, m_isForcedSupported, bool(Gametype this))
+    {
+        // force all DM maps to work in duel?!
+        // TODO: we should really check the size of maps, some DM maps do not work for duel!
+        if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags))
+            return true;
+        return false;
+    }
+ENDCLASS(Duel)
+REGISTER_GAMETYPE(DUEL, NEW(Duel));
+#define g_duel IS_GAMETYPE(DUEL)
+
 const int MAPINFO_FEATURE_WEAPONS       = 1; // not defined for instagib-only maps
 const int MAPINFO_FEATURE_VEHICLES      = 2;
 const int MAPINFO_FEATURE_TURRETS       = 4;
@@ -550,4 +674,4 @@ void MapInfo_ClearTemps(); // call this when done with mapinfo for this frame
 void MapInfo_Shutdown(); // call this in the shutdown handler
 
 #define MAPINFO_SETTEMP_ACL_USER cvar_string("g_mapinfo_settemp_acl")
-#define MAPINFO_SETTEMP_ACL_SYSTEM "-g_mapinfo_* -rcon_* -_* -g_ban* +*"
+#define MAPINFO_SETTEMP_ACL_SYSTEM "-g_mapinfo_* -rcon_* -_* -g_ban* -r_water +*"
index b647e15a826e48164f03db46c5f62114a8e6afc6..60920fafb8c5c95599af7b2090570bfc30ae84d6 100644 (file)
@@ -48,6 +48,8 @@ spawnfunc(func_bobbing)
 
        this.active = ACTIVE_ACTIVE;
 
+       this.draggable = drag_undraggable;
+
        // damage when blocked
        setblocked(this, generic_plat_blocked);
        if(this.dmg && (this.message == ""))
index 44e31284336aae99eb35f580dff390b5db98797b..024d5cfd873cfb37d288785876d4e4c0684a681f 100644 (file)
@@ -5,6 +5,43 @@
 void button_wait(entity this);
 void button_return(entity this);
 
+// in case button is deactivated by a relay_deactivate while it pressed down
+// set both fields to -1 in button_return!!
+.float wait_remaining;
+.float activation_time;
+
+void button_setactive(entity this, int astate)
+{
+       int oldstate = this.active;
+       if (astate == ACTIVE_TOGGLE)
+       {
+               if (this.active == ACTIVE_ACTIVE)
+                       this.active = ACTIVE_NOT;
+               else
+                       this.active = ACTIVE_ACTIVE;
+       }
+       else
+               this.active = astate;
+
+       if (this.active == ACTIVE_ACTIVE && oldstate == ACTIVE_NOT)
+       {
+               // button was deactivated while it was pressed
+               if (this.wait_remaining >= 0)
+               {
+                       this.nextthink =  this.wait_remaining + this.ltime;
+                       setthink(this, button_return);
+               }
+       }
+       else if (this.active == ACTIVE_NOT && oldstate == ACTIVE_ACTIVE)
+       {
+               // check if button is in pressed state
+               if (this.activation_time >= 0)
+               {
+                       this.wait_remaining = this.wait - (time - this.activation_time);
+               }
+       }
+}
+
 void button_wait(entity this)
 {
        this.state = STATE_TOP;
@@ -24,11 +61,17 @@ void button_done(entity this)
 
 void button_return(entity this)
 {
+       if (this.active != ACTIVE_ACTIVE)
+       {
+               return;
+       }
        this.state = STATE_DOWN;
        SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, button_done);
        this.frame = 0;                 // use normal textures
        if (GetResourceAmount(this, RESOURCE_HEALTH))
                this.takedamage = DAMAGE_YES;   // can be shot again
+       this.wait_remaining = -1;
+       this.activation_time = -1;
 }
 
 
@@ -46,6 +89,8 @@ void button_fire(entity this)
        if (this.state == STATE_UP || this.state == STATE_TOP)
                return;
 
+       this.activation_time = time;
+
        if (this.noise != "")
                _sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
 
@@ -60,6 +105,9 @@ void button_reset(entity this)
        this.frame = 0;                 // use normal textures
        this.state = STATE_BOTTOM;
        this.velocity = '0 0 0';
+       this.wait_remaining = -1;
+       this.activation_time = -1;
+       this.active = ACTIVE_ACTIVE;
        setthink(this, func_null);
        this.nextthink = 0;
        if (GetResourceAmount(this, RESOURCE_HEALTH))
@@ -77,6 +125,8 @@ void button_use(entity this, entity actor, entity trigger)
 
 void button_touch(entity this, entity toucher)
 {
+       if (this.active != ACTIVE_ACTIVE)
+               return;
        if (!toucher)
                return;
        if (!toucher.iscreature)
@@ -91,6 +141,8 @@ void button_touch(entity this, entity toucher)
 
 void button_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
+       if (this.active != ACTIVE_ACTIVE)
+               return;
        if(this.spawnflags & NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
@@ -157,7 +209,9 @@ spawnfunc(func_button)
     if(this.noise != "")
         precache_sound(this.noise);
 
-       this.active = ACTIVE_ACTIVE;
+       this.draggable = drag_undraggable;
+
+       this.setactive = button_setactive;
 
        this.pos1 = this.origin;
        this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
index 9ad326cfa225f7ef9b0c08151e96f4feeacdff39..4c40598d35339676829c7f1de7f69a92de7f5545 100644 (file)
@@ -106,8 +106,6 @@ void conveyor_init(entity this)
        this.reset = generic_netlinked_reset;
        this.reset(this);
 
-       FixSize(this);
-
        Net_LinkEntity(this, 0, false, conveyor_send);
 
        this.SendFlags |= SF_TRIGGER_INIT;
index 8d40a377be081fbc583f3adef2486a129530d694..1ba7bad3aa633a12556f1476ad5f5efb78eb471b 100644 (file)
@@ -277,7 +277,7 @@ void door_damage(entity this, entity inflictor, entity attacker, float damage, i
        {
                SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
                this.owner.takedamage = DAMAGE_NO;      // will be reset upon return
-               door_use(this.owner, NULL, NULL);
+               door_use(this.owner, attacker, NULL);
        }
 }
 
@@ -595,8 +595,6 @@ float door_send(entity this, entity to, float sf)
 
 void door_link()
 {
-       // set size now, as everything is loaded
-       //FixSize(this);
        //Net_LinkEntity(this, false, 0, door_send);
 }
 #endif
@@ -715,9 +713,6 @@ spawnfunc(func_door)
        setblocked(this, door_blocked);
        this.use = door_use;
 
-       this.pos1 = this.origin;
-       this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
-
        if(this.spawnflags & DOOR_NONSOLID)
                this.solid = SOLID_NOT;
 
@@ -728,6 +723,9 @@ spawnfunc(func_door)
 
        door_init_shared(this);
 
+       this.pos1 = this.origin;
+       this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
+
        if (!this.speed)
        {
                this.speed = 100;
index b052336217990a32a646d852679bc3c0d1d83c20..53dbed02fa42a50580b69efe7ef80143ba0b0f5e 100644 (file)
@@ -103,6 +103,7 @@ spawnfunc(func_plat)
        this.angles = '0 0 0';
 
        this.classname = "plat";
+       this.draggable = drag_undraggable;
        if (!InitMovingBrushTrigger(this))
                return;
        this.effects |= EF_LOWPRECISION;
index 498f6c521e941c861d7049855699324bfd502666..c7f1619ad54faba790ac59e5533974e7d1e7a495 100644 (file)
@@ -2,5 +2,6 @@
 #include <common/mapobjects/misc/corner.qc>
 #include <common/mapobjects/misc/dynlight.qc>
 #include <common/mapobjects/misc/follow.qc>
+#include <common/mapobjects/misc/keys.qc>
 #include <common/mapobjects/misc/laser.qc>
 #include <common/mapobjects/misc/teleport_dest.qc>
index 3415919f8993799be9acc23fdb1c16a54a6508ef..617db807b9c1f3d68e6e1209bcff3e7dc0afcd17 100644 (file)
@@ -2,5 +2,6 @@
 #include <common/mapobjects/misc/corner.qh>
 #include <common/mapobjects/misc/dynlight.qh>
 #include <common/mapobjects/misc/follow.qh>
+#include <common/mapobjects/misc/keys.qh>
 #include <common/mapobjects/misc/laser.qh>
 #include <common/mapobjects/misc/teleport_dest.qh>
diff --git a/qcsrc/common/mapobjects/misc/keys.qc b/qcsrc/common/mapobjects/misc/keys.qc
new file mode 100644 (file)
index 0000000..2c85742
--- /dev/null
@@ -0,0 +1,292 @@
+#include "keys.qh"
+
+#ifdef CSQC
+bool item_keys_usekey(entity l, entity p)
+{
+       int valid = (l.itemkeys & p.itemkeys); // TODO: itemkeys isn't networked or anything!
+       l.itemkeys &= ~valid; // only some of the needed keys were given
+       return valid != 0;
+}
+#endif
+
+#ifdef SVQC
+/*
+TODO:
+- add an unlock sound (here to trigger_keylock and to func_door)
+- display available keys on the HUD
+- make more tests
+- think about adding NOT_EASY/NOT_NORMAL/NOT_HARD for Q1 compatibility
+- should keys have a trigger?
+*/
+
+bool item_keys_usekey(entity l, entity p)
+{
+       int valid = l.itemkeys & p.itemkeys;
+
+       if (!valid) {
+               // player has none of the needed keys
+               return false;
+       } else if (l.itemkeys == valid) {
+               // ALL needed keys were given
+               l.itemkeys = 0;
+               return true;
+       } else {
+               // only some of the needed keys were given
+               l.itemkeys &= ~valid;
+               return true;
+       }
+}
+
+string item_keys_keylist(float keylist) {
+       // no keys
+       if (!keylist)
+               return "";
+
+       // one key
+       if ((keylist & (keylist-1)) == 0)
+               return strcat("the ", item_keys_names[lowestbit(keylist)]);
+
+       string n = "";
+       int base = 0;
+       while (keylist) {
+               int l = lowestbit(keylist);
+               if (n)
+                       n = strcat(n, ", the ", item_keys_names[base + l]);
+               else
+                       n = strcat("the ", item_keys_names[base + l]);
+
+               keylist = bitshift(keylist,  -(l + 1));
+               base+= l + 1;
+       }
+
+       return n;
+}
+
+
+/*
+================================
+item_key
+================================
+*/
+
+/**
+ * Key touch handler.
+ */
+void item_key_touch(entity this, entity toucher)
+{
+       if (!IS_PLAYER(toucher))
+               return;
+
+       // player already picked up this key
+       if (PS(toucher).itemkeys & this.itemkeys)
+               return;
+
+       PS(toucher).itemkeys |= this.itemkeys;
+       play2(toucher, this.noise);
+
+       centerprint(toucher, this.message);
+
+       string oldmsg = this.message;
+       this.message = "";
+       SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
+       this.message = oldmsg;
+}
+
+/**
+ * Spawn a key with given model, key code and color.
+ */
+void spawn_item_key(entity this)
+{
+       precache_model(this.model);
+
+       if (this.spawnflags & 1) // FLOATING
+               this.noalign = 1;
+
+       if (this.noalign)
+               set_movetype(this, MOVETYPE_NONE);
+       else
+               set_movetype(this, MOVETYPE_TOSS);
+
+       precache_sound(this.noise);
+
+       this.mdl = this.model;
+       this.effects = EF_LOWPRECISION;
+       _setmodel(this, this.model);
+       //setsize(this, '-16 -16 -24', '16 16 32');
+       setorigin(this, this.origin + '0 0 32');
+       setsize(this, '-16 -16 -56', '16 16 0');
+       this.modelflags |= MF_ROTATE;
+       this.solid = SOLID_TRIGGER;
+
+       if (!this.noalign)
+       {
+               // first nudge it off the floor a little bit to avoid math errors
+               setorigin(this, this.origin + '0 0 1');
+               // note droptofloor returns false if stuck/or would fall too far
+               droptofloor(this);
+       }
+
+       settouch(this, item_key_touch);
+}
+
+
+/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+A key entity.
+The itemkeys should contain one of the following key IDs:
+1 - GOLD key -
+2 - SILVER key
+4 - BRONZE key
+8 - RED keycard
+16 - BLUE keycard
+32 - GREEN keycard
+Custom keys:
+... - last key is 1<<23
+Keys with bigger Id than 32 don't have a default netname and model, if you use one of them, you MUST provide those.
+-----------KEYS------------
+colormod: color of the key (default: '.9 .9 .9').
+itemkeys: a key Id.
+message: message to print when player picks up this key.
+model: custom key model to use.
+netname: the display name of the key.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+This is the only correct way to put keys on the map!
+
+itemkeys MUST always have exactly one bit set.
+*/
+spawnfunc(item_key)
+{
+       string _netname;
+       vector _colormod;
+
+       // reject this entity if more than one key was set!
+       if (this.itemkeys>0 && (this.itemkeys & (this.itemkeys-1)) != 0) {
+               objerror(this, "item_key.itemkeys must contain only 1 bit set specifying the key it represents!");
+               delete(this);
+               return;
+       }
+
+       // find default netname and colormod
+       switch(this.itemkeys) {
+       case BIT(0):
+               _netname = "GOLD key";
+               _colormod = '1 .9 0';
+               break;
+
+       case BIT(1):
+               _netname = "SILVER key";
+               _colormod = '.9 .9 .9';
+               break;
+
+       case BIT(2):
+               _netname = "BRONZE key";
+               _colormod = '.6 .25 0';
+               break;
+
+       case BIT(3):
+               _netname = "RED keycard";
+               _colormod = '.9 0 0';
+               break;
+
+       case BIT(4):
+               _netname = "BLUE keycard";
+               _colormod = '0 0 .9';
+               break;
+
+       case BIT(5):
+               _netname = "GREEN keycard";
+               _colormod = '0 .9 0';
+               break;
+
+       default:
+               _netname = "FLUFFY PINK keycard";
+               _colormod = '1 1 1';
+
+               if (this.netname == "") {
+                       objerror(this, "item_key doesn't have a default name for this key and a custom one was not specified!");
+                       delete(this);
+                       return;
+               }
+               break;
+
+       }
+
+       // find default model
+       string _model = string_null;
+       if (this.itemkeys <= ITEM_KEY_BIT(2)) {
+               _model = "models/keys/key.md3";
+       } else if (this.itemkeys >= ITEM_KEY_BIT(3) && this.itemkeys <= ITEM_KEY_BIT(5)) {
+               _model = "models/keys/key.md3"; // FIXME: replace it by a keycard model!
+       } else if (this.model == "") {
+               objerror(this, "item_key doesn't have a default model for this key and a custom one was not specified!");
+               delete(this);
+               return;
+       }
+
+       // set defailt netname
+       if (this.netname == "")
+               this.netname = _netname;
+
+       // set default colormod
+       if (!this.colormod)
+               this.colormod = _colormod;
+
+       // set default model
+       if (this.model == "")
+               this.model = _model;
+
+       // set default pickup message
+       if (this.message == "")
+               this.message = strzone(strcat("You've picked up the ", this.netname, "!"));
+
+       if (this.noise == "")
+               this.noise = strzone(SND(ITEMPICKUP));
+
+       // save the name for later
+       item_keys_names[lowestbit(this.itemkeys)] = this.netname;
+
+       // put the key on the map
+       spawn_item_key(this);
+}
+
+/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+SILVER key.
+-----------KEYS------------
+colormod: color of the key (default: '.9 .9 .9').
+message: message to print when player picks up this key.
+model: custom model to use.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+Don't use this entity on new maps! Use item_key instead.
+*/
+spawnfunc(item_key1)
+{
+       this.classname = "item_key";
+       this.itemkeys = ITEM_KEY_BIT(1);
+       spawnfunc_item_key(this);
+}
+
+/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
+GOLD key.
+-----------KEYS------------
+colormod: color of the key (default: '1 .9 0').
+message: message to print when player picks up this key.
+model: custom model to use.
+noise: custom sound to play when player picks up the key.
+-------- SPAWNFLAGS --------
+FLOATING: the item will float in air, instead of aligning to the floor by falling
+---------NOTES----------
+Don't use this entity on new maps! Use item_key instead.
+*/
+spawnfunc(item_key2)
+{
+       this.classname = "item_key";
+       this.itemkeys = ITEM_KEY_BIT(0);
+       spawnfunc_item_key(this);
+}
+
+#endif
diff --git a/qcsrc/common/mapobjects/misc/keys.qh b/qcsrc/common/mapobjects/misc/keys.qh
new file mode 100644 (file)
index 0000000..50be5f8
--- /dev/null
@@ -0,0 +1,26 @@
+#pragma once
+
+/**
+ * Returns the bit ID of a key
+ */
+#define ITEM_KEY_BIT(n)        ( bitshift(1, n) )
+
+#define ITEM_KEY_MAX   24
+
+/**
+ * list of key names.
+ */
+#ifdef SVQC
+string item_keys_names[ITEM_KEY_MAX];
+
+/**
+ * Use keys from p on l.
+ * Returns true if any new keys were given, false otherwise.
+ */
+float item_keys_usekey(entity l, entity p);
+
+/**
+ * Returns a string with a comma separated list of key names, as specified in keylist.
+ */
+string item_keys_keylist(float keylist);
+#endif
index 92ff464b71016951d4f675e0c667b11429b5e13f..10c3900408e1dd3cd818f9c2715729c5c52060b7 100644 (file)
@@ -41,6 +41,11 @@ void g_clientmodel_setcolormaptoactivator(entity this, entity actor, entity trig
 
 void g_clientmodel_use(entity this, entity actor, entity trigger)
 {
+       // Flag to set func_clientwall state
+       // 1 == deactivate, 2 == activate, 0 == do nothing
+       if(this.classname == "func_clientwall" || this.classname == "func_clientillusionary")
+               this.antiwall_flag = trigger.antiwall_flag;
+
        if (this.antiwall_flag == 1)
        {
                this.inactive = 1;
@@ -148,10 +153,10 @@ bool g_clientmodel_genericsendentity(entity this, entity to, int sf)
                        WriteVector(MSG_ENTITY, this.movedir);
                        WriteByte(MSG_ENTITY, floor(this.lip * 255));
                }
-               WriteByte(MSG_ENTITY, this.fade_start);
-               WriteByte(MSG_ENTITY, this.fade_end);
-               WriteByte(MSG_ENTITY, this.alpha_max);
-               WriteByte(MSG_ENTITY, this.alpha_min);
+               WriteShort(MSG_ENTITY, bound(0, this.fade_start, 65535));
+               WriteShort(MSG_ENTITY, bound(0, this.fade_end, 65535));
+               WriteByte(MSG_ENTITY, floor(this.alpha_max * 256));
+               WriteByte(MSG_ENTITY, floor(this.alpha_min * 256));
                WriteByte(MSG_ENTITY, this.inactive);
                WriteShort(MSG_ENTITY, this.fade_vertical_offset);
        }
@@ -199,42 +204,42 @@ spawnfunc(func_clientwall)        { G_CLIENTMODEL_INIT(this, SOLID_BSP) } // bru
 
 void Ent_Wall_PreDraw(entity this)
 {
+       float alph = this.alpha;
        if (this.inactive)
        {
-               this.alpha = 0;
+               alph = 0;
        }
        else
        {
                vector org = getpropertyvec(VF_ORIGIN);
                if(!checkpvs(org, this))
-                       this.alpha = 0;
+                       alph = 0;
                else if(this.fade_start || this.fade_end) {
                        vector offset = '0 0 0';
                        offset_z = this.fade_vertical_offset;
-                       float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset);
+                       vector player_dist_math = org - this.origin - 0.5 * (this.mins + this.maxs) + offset;
                        if (this.fade_end == this.fade_start)
                        {
-                               if (player_dist >= this.fade_start)
-                                       this.alpha = 0;
+                               if (vdist(player_dist_math, >=, this.fade_start))
+                                       alph = 0;
                                else
-                                       this.alpha = 1;
+                                       alph = 1;
                        }
                        else
                        {
-                               this.alpha = (this.alpha_min + this.alpha_max * bound(0,
+                               float player_dist = vlen(player_dist_math);
+                               alph = (this.alpha_min + this.alpha_max * bound(0,
                                                           (this.fade_end - player_dist)
                                                           / (this.fade_end - this.fade_start), 1)) / 100.0;
                        }
                }
                else
                {
-                       this.alpha = 1;
+                       alph = 1;
                }
        }
-       if(this.alpha <= 0)
-               this.drawmask = 0;
-       else
-               this.drawmask = MASK_NORMAL;
+       this.alpha = alph;
+       this.drawmask = (alph <= 0) ? 0 : MASK_NORMAL;
 }
 
 void Ent_Wall_Draw(entity this)
@@ -390,10 +395,10 @@ NET_HANDLE(ENT_CLIENT_WALL, bool isnew)
                        this.movedir = ReadVector();
                        this.lip = ReadByte() / 255.0;
                }
-               this.fade_start = ReadByte();
-               this.fade_end = ReadByte();
-               this.alpha_max = ReadByte();
-               this.alpha_min = ReadByte();
+               this.fade_start = ReadShort();
+               this.fade_end = ReadShort();
+               this.alpha_max = ReadByte() / 255.0;
+               this.alpha_min = ReadByte() / 255.0;
                this.inactive = ReadByte();
                this.fade_vertical_offset = ReadShort();
                BGMScript_InitEntity(this);
index 50170e251b5fb8cb518fe24e7fe1fbd22b08f34a..45346dc8ea70440affb817baa6e4ddd8e5888f5e 100644 (file)
@@ -9,7 +9,7 @@ classfield(Wall) .float loddistance1, loddistance2;
 classfield(Wall) .vector saved;
 
 // Needed for interactive clientwalls
-.float inactive; // Clientwall disappears when inactive
+.bool inactive; // Clientwall disappears when inactive
 .float alpha_max, alpha_min;
 // If fade_start > fade_end, fadeout will be inverted
 // fade_vertical_offset is a vertival offset for player position
index 0fa7db2f1c6eab6a59bc39ad39e4dbaf348d08a2..861d73e72f28d6522ea9c488d91a6ae465f6a87d 100644 (file)
@@ -24,6 +24,7 @@ SUB_SetFade
 Fade 'ent' out when time >= 'when'
 ==================
 */
+.float fade_rate;
 void SUB_SetFade(entity ent, float when, float fading_time);
 
 .vector                finaldest, finalangle;          //plat.qc stuff
index 4c89c4c27ed20efc86f73a678c615d68aa82673b..9156439f99e3bf19bb9bdc8425efe1fdcd06d53d 100644 (file)
@@ -4,13 +4,21 @@ void counter_reset(entity this);
 
 void counter_use(entity this, entity actor, entity trigger)
 {
-       this.count -= 1;
-       if (this.count < 0)
+       entity store = this;
+       if(this.spawnflags & COUNTER_PER_PLAYER)
+       {
+               if(!IS_PLAYER(actor))
+                       return;
+               store = actor;
+       }
+
+       store.counter_cnt += 1;
+       if (store.counter_cnt > this.count)
                return;
 
        bool doactivate = (this.spawnflags & COUNTER_FIRE_AT_COUNT);
 
-       if (this.count == 0)
+       if (store.counter_cnt == this.count)
        {
                if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
                        Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
@@ -27,10 +35,10 @@ void counter_use(entity this, entity actor, entity trigger)
        {
                if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE))
                {
-                       if(this.count >= 4)
+                       if((this.count - store.counter_cnt) >= 4)
                                Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
                        else
-                               Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count);
+                               Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count - store.counter_cnt);
                }
        }
 
@@ -42,7 +50,7 @@ void counter_reset(entity this)
 {
        setthink(this, func_null);
        this.nextthink = 0;
-       this.count = this.cnt;
+       this.counter_cnt = 0;
 }
 
 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage COUNTER_FIRE_AT_COUNT
@@ -59,8 +67,8 @@ spawnfunc(trigger_counter)
 {
        if (!this.count)
                this.count = 2;
-       this.cnt = this.count;
 
+       this.counter_cnt = 0;
        this.use = counter_use;
        this.reset = counter_reset;
 }
index 394d15472cdbc68f289b9476ec00311e6dd22e09..d36bd0293cc933ead43f3e2248b46d157cdc91c3 100644 (file)
@@ -1,4 +1,10 @@
 #pragma once
 
+#ifdef SVQC
+spawnfunc(trigger_counter);
+
+.float counter_cnt;
+#endif
 
 const int COUNTER_FIRE_AT_COUNT = BIT(2);
+const int COUNTER_PER_PLAYER = BIT(3);
index 966e0cfb0fe6657287ee8135c9f9411f732de18d..ccdf2c7d0b96a291086be949e2c53db8180b725d 100644 (file)
@@ -25,7 +25,7 @@ void trigger_hurt_touch(entity this, entity toucher)
                if (toucher.triggerhurttime < time)
                {
                        EXACTTRIGGER_TOUCH(this, toucher);
-                       toucher.triggerhurttime = time + 1;
+                       toucher.triggerhurttime = time + ((autocvar_sv_vq3compat && !(this.spawnflags & HURT_SLOW)) ? 0.1 : 1);
 
                        entity own;
                        own = this.enemy;
@@ -53,7 +53,7 @@ void trigger_hurt_touch(entity this, entity toucher)
 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
 Any object touching this will be hurt
 set dmg to damage amount
-default dmg = 1000
+default dmg = 10000
 */
 .entity trigger_hurt_next;
 entity trigger_hurt_last;
@@ -66,7 +66,7 @@ spawnfunc(trigger_hurt)
        this.use = trigger_hurt_use;
        this.enemy = world; // I hate you all
        if (!this.dmg)
-               this.dmg = 1000;
+               this.dmg = ((autocvar_sv_vq3compat) ? 5 : 10000);
        if (this.message == "")
                this.message = "was in the wrong place";
        if (this.message2 == "")
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..e992154aa97fd6a76cd6d7fc6d856ebb356eda4a 100644 (file)
@@ -1 +1,3 @@
 #pragma once
+
+const int HURT_SLOW = BIT(4);
index 5ffdf2d1066085d9bd47fd1dd5d541af179b4671..ca1faeaf93c2ddd97e8c3ef052c3d770122cde26 100644 (file)
@@ -133,9 +133,20 @@ bool jumppad_push(entity this, entity targ)
        if (!isPushable(targ))
                return false;
 
+       vector org = targ.origin;
+#ifdef SVQC
+       if(autocvar_sv_vq3compat)
+#elif defined(CSQC)
+       if(STAT(VQ3COMPAT))
+#endif
+       {
+               org.z += targ.mins_z;
+               org.z += 1; // off by 1!
+       }
+
        if(this.enemy)
        {
-               targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ);
+               targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
        }
        else if(this.target && this.target != "")
        {
@@ -148,7 +159,7 @@ bool jumppad_push(entity this, entity targ)
                        else
                                RandomSelection_AddEnt(e, 1, 1);
                }
-               targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ);
+               targ.velocity = trigger_push_calculatevelocity(org, RandomSelection_chosen_ent, this.height, targ);
        }
        else
        {
index cd6adec310ccf2c00b974f92329f519f0561b45e..268134e806757aa4f459295ad818bb4a8db013bc 100644 (file)
@@ -8,7 +8,7 @@ IntrusiveList g_jumppads;
 STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); }
 
 .float pushltime;
-.float istypefrag;
+.bool istypefrag;
 .float height;
 
 const int NUM_JUMPPADSUSED = 3;
index 67db14421662c2d0c40483b328ee3dd30ee6babe..f7ecd7c1f1ab8c5c0e3afd1e202085c2e27fd460 100644 (file)
@@ -30,7 +30,14 @@ void trigger_keylock_touch(entity this, entity toucher)
 
        // check silver key
        if(this.itemkeys)
-               key_used = item_keys_usekey(this, toucher);
+       {
+#ifdef SVQC
+               entity store = PS(toucher);
+#elif defined(CSQC)
+               entity store = toucher;
+#endif
+               key_used = item_keys_usekey(this, store);
+       }
 
        if(this.itemkeys)
        {
index 904c3fa3d4a7404d5d4c0535fbc59858b2d6cc42..6f70f09beec2219624baeca92e2cd7deaa104fb4 100644 (file)
@@ -1,10 +1 @@
 #pragma once
-
-#ifdef CSQC
-bool item_keys_usekey(entity l, entity p)
-{
-       int valid = (l.itemkeys & p.itemkeys); // TODO: itemkeys isn't networked or anything!
-       l.itemkeys &= ~valid; // only some of the needed keys were given
-       return valid != 0;
-}
-#endif
index 6968a6556a0ce2a18571a647603c228148c9bb4b..9a7181d3a2250a530466a1d3dcbf59a9e9d1f0d8 100644 (file)
@@ -1,7 +1,4 @@
 #include "triggers.qh"
-#ifdef SVQC
-       #include <server/item_key.qh>
-#endif
 
 void SUB_DontUseTargets(entity this, entity actor, entity trigger) { }
 
@@ -13,17 +10,6 @@ void DelayThink(entity this)
        delete(this);
 }
 
-void FixSize(entity e)
-{
-       e.mins_x = rint(e.mins_x);
-       e.mins_y = rint(e.mins_y);
-       e.mins_z = rint(e.mins_z);
-
-       e.maxs_x = rint(e.maxs_x);
-       e.maxs_y = rint(e.maxs_y);
-       e.maxs_z = rint(e.maxs_z);
-}
-
 #ifdef SVQC
 void generic_setactive(entity this, int act)
 {
@@ -79,7 +65,7 @@ void generic_netlinked_reset(entity this)
 // Compatibility with old maps
 void generic_netlinked_legacy_use(entity this, entity actor, entity trigger)
 {
-       LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname);
+       //LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname);
        this.setactive(this, ACTIVE_TOGGLE);
 }
 
@@ -316,12 +302,9 @@ void SUB_UseTargets_Ex(entity this, entity actor, entity trigger, bool preventRe
                }
                if (s != "")
                {
-                       // Flag to set func_clientwall state
-                       // 1 == deactivate, 2 == activate, 0 == do nothing
-                       int aw_flag = this.antiwall_flag;
                        for(entity t = NULL; (t = find(t, targetname, s)); )
                        {
-                               if(t.use && (t.sub_target_used != time || !preventReuse))
+                               if(t != this && t.use && (t.sub_target_used != time || !preventReuse))
                                {
                                        if(this.target_random)
                                        {
@@ -329,9 +312,6 @@ void SUB_UseTargets_Ex(entity this, entity actor, entity trigger, bool preventRe
                                        }
                                        else
                                        {
-                                               if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary")
-                                                       t.antiwall_flag = aw_flag;
-
                                                t.use(t, actor, this);
                                                if(preventReuse)
                                                        t.sub_target_used = time;
index 82e7d54f0283781eb42fde552e99adfc14c6b4da..b9baf63f1c70ed30fcaaaf89559d298b45c93084 100644 (file)
@@ -40,8 +40,6 @@ void generic_netlinked_legacy_use(entity this, entity actor, entity trigger);
 
 .vector dest;
 
-void FixSize(entity e);
-
 #ifdef CSQC
 void trigger_common_read(entity this, bool withtarget);
 void trigger_remove_generic(entity this);
index 2049dc9eae601b8b03598fb74c2e49efbcba65bf..f299af3c3a086c2cae4735b989c2c7396044a77a 100644 (file)
@@ -357,9 +357,6 @@ void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger)
                HUD_MinigameMenu_entries = NULL;
                HUD_MinigameMenu_last_entry = NULL;
                HUD_MinigameMenu_activeitem = NULL;
-               if(autocvar_hud_cursormode)
-               if ( !autocvar__hud_configure )
-                       setcursormode(0);
        }
 }
 
@@ -420,8 +417,6 @@ void HUD_MinigameMenu_Open()
                        HUD_MinigameMenu_last_entry );
                HUD_MinigameMenu_CurrentButton();
                HUD_MinigameMenu_activeitem = NULL;
-               if(autocvar_hud_cursormode)
-                       setcursormode(1);
        }
 }
 
@@ -683,11 +678,6 @@ void HUD_Minigame_Mouse()
        if( !HUD_MinigameMenu_IsOpened() || autocvar__hud_configure || mv_active )
                return;
 
-       if (!autocvar_hud_cursormode)
-               update_mousepos();
-
        if ( HUD_MinigameMenu_IsOpened() && HUD_mouse_over(HUD_PANEL(MINIGAMEMENU)) )
                HUD_MinigameMenu_MouseInput();
-
-       draw_cursor_normal(mousepos, '1 1 1', panel_fg_alpha);
 }
index b4195c05774eec2f08d780de5b4ddbff6cc687c5..91fa9cbda908509116acab6ed4c13adc022682cd 100644 (file)
@@ -1370,7 +1370,7 @@ int bd_client_event(entity minigame, string event, ...)
                                {
                                        sent.message = bd_turn_to_string(sent.minigame_flags);
                                        //if ( sent.minigame_flags & minigame_self.team )
-                                               minigame_prompt();
+                                               //minigame_prompt();
                                }
                        }
                        else if(sent.classname == "minigame_board_piece")
index c8851f2ac4bad90e9f437751a20482182736050c..b5b900b8a18a1d10fbf959368b92f8321f512db4 100644 (file)
@@ -606,8 +606,8 @@ int ps_client_event(entity minigame, string event, ...)
                                if ( sf & MINIG_SF_UPDATE )
                                {
                                        sent.message = ps_turn_to_string(sent.minigame_flags);
-                                       if ( sent.minigame_flags & minigame_self.team )
-                                               minigame_prompt();
+                                       //if ( sent.minigame_flags & minigame_self.team )
+                                               //minigame_prompt();
                                }
                        }
 
index 5c6af26615dc5f71363dfb9286eab23da294a859..af74e6a5a6c601902c1da414b0a24d8d46f356b4 100644 (file)
@@ -9,7 +9,7 @@ void player_clear_minigame(entity player)
                set_movetype(player, MOVETYPE_WALK);
        else
                set_movetype(player, MOVETYPE_FLY_WORLDONLY);
-       player.team_forced = 0;
+       Player_SetForcedTeamIndex(player, TEAM_FORCE_DEFAULT);
 }
 
 void minigame_rmplayer(entity minigame_session, entity player)
@@ -150,7 +150,7 @@ int minigame_addplayer(entity minigame_session, entity player)
                        PutObserverInServer(player);
                }
                if ( autocvar_sv_minigames_observer == 2 )
-                       player.team_forced = -1;
+                       Player_SetForcedTeamIndex(player, TEAM_FORCE_SPECTATOR);
 
                minigame_resend(minigame_session);
        }
index 5e2cc0513851594d70a64167656be7196135500d..d847cf4e6cd2942494bb8beeb61d45abae40ad81 100644 (file)
@@ -168,9 +168,9 @@ void M_Spider_Attack_Web(entity this)
 
 bool M_Spider_Attack(int attack_type, entity actor, entity targ, .entity weaponentity)
 {
+       Weapon wep = WEP_SPIDER_ATTACK;
        switch(attack_type)
        {
-               Weapon wep = WEP_SPIDER_ATTACK;
                case MONSTER_ATTACK_MELEE:
                {
                        wep.wr_think(wep, actor, weaponentity, 2);
index 84355c7f3530ffddcf3dccde86781d487c7fd717..004fbf92b659a8c5641f20f8eea173770c676771 100644 (file)
@@ -695,7 +695,6 @@ void Monster_CalculateVelocity(entity this, vector to, vector from, float turnra
 }
 
 .entity draggedby;
-.entity target2;
 
 void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
 {
@@ -895,7 +894,7 @@ void Monster_Reset(entity this)
        setorigin(this, this.pos1);
        this.angles = this.pos2;
 
-       Unfreeze(this); // remove any icy remains
+       Unfreeze(this, false); // remove any icy remains
 
        SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        this.velocity = '0 0 0';
@@ -931,10 +930,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
        this.monster_lifetime = time + 5;
 
        if(STAT(FROZEN, this))
-       {
-               Unfreeze(this); // remove any icy remains
-               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // reset by Unfreeze (TODO)
-       }
+               Unfreeze(this, false); // remove any icy remains
 
        monster_dropitem(this, attacker);
 
@@ -1161,7 +1157,7 @@ void Monster_Anim(entity this)
 
 void Monster_Frozen_Think(entity this)
 {
-       if(STAT(FROZEN, this) == 2)
+       if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
                SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health));
@@ -1171,9 +1167,9 @@ void Monster_Frozen_Think(entity this)
                        WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
 
                if(STAT(REVIVE_PROGRESS, this) >= 1)
-                       Unfreeze(this);
+                       Unfreeze(this, false);
        }
-       else if(STAT(FROZEN, this) == 3)
+       else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - this.ticrate * this.revive_speed, 1);
                SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
@@ -1183,14 +1179,12 @@ void Monster_Frozen_Think(entity this)
 
                if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
                {
-                       Unfreeze(this);
-                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                       Unfreeze(this, false);
                        if(this.event_damage)
                                this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
                }
-
                else if ( STAT(REVIVE_PROGRESS, this) <= 0 )
-                       Unfreeze(this);
+                       Unfreeze(this, false);
        }
        // otherwise, no revival!
 
index 877d5c4eac2630e6a55caf1d4a541d7726a7d60a..0c9b8890189c7337f774db335c7555c5d40eaea2 100644 (file)
@@ -12,8 +12,8 @@ string Buff_UndeprecateName(string buffname)
 }
 
 REGISTER_BUFF(AMMO) {
-    this.m_prettyName = _("Ammo");
-    this.m_name = "ammo";
+    this.m_name = _("Ammo");
+    this.netname = "ammo";
     this.m_skin = 3;
     this.m_color = '0.76 1 0.1';
 }
@@ -21,8 +21,8 @@ BUFF_SPAWNFUNCS(ammo, BUFF_AMMO)
 BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO)
 
 REGISTER_BUFF(RESISTANCE) {
-    this.m_prettyName = _("Resistance");
-    this.m_name = "resistance";
+    this.m_name = _("Resistance");
+    this.netname = "resistance";
     this.m_skin = 0;
     this.m_color = '0.36 1 0.07';
 }
@@ -30,8 +30,8 @@ BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE)
 BUFF_SPAWNFUNC_Q3TA_COMPAT(resistance, BUFF_RESISTANCE)
 
 REGISTER_BUFF(SPEED) {
-    this.m_prettyName = _("Speed");
-    this.m_name = "speed";
+    this.m_name = _("Speed");
+    this.netname = "speed";
     this.m_skin = 9;
     this.m_color = '0.1 1 0.84';
 }
@@ -40,8 +40,8 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(haste, BUFF_SPEED)
 BUFF_SPAWNFUNC_Q3TA_COMPAT(scout, BUFF_SPEED)
 
 REGISTER_BUFF(MEDIC) {
-    this.m_prettyName = _("Medic");
-    this.m_name = "medic";
+    this.m_name = _("Medic");
+    this.netname = "medic";
     this.m_skin = 1;
     this.m_color = '1 0.12 0';
 }
@@ -51,40 +51,40 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(regen, BUFF_MEDIC)
 BUFF_SPAWNFUNC_Q3TA_COMPAT(revival, BUFF_MEDIC)
 
 REGISTER_BUFF(BASH) {
-    this.m_prettyName = _("Bash");
-    this.m_name = "bash";
+    this.m_name = _("Bash");
+    this.netname = "bash";
     this.m_skin = 5;
     this.m_color = '1 0.39 0';
 }
 BUFF_SPAWNFUNCS(bash, BUFF_BASH)
 
 REGISTER_BUFF(VAMPIRE) {
-    this.m_prettyName = _("Vampire");
-    this.m_name = "vampire";
+    this.m_name = _("Vampire");
+    this.netname = "vampire";
     this.m_skin = 2;
     this.m_color = '1 0 0.24';
 }
 BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE)
 
 REGISTER_BUFF(DISABILITY) {
-    this.m_prettyName = _("Disability");
-    this.m_name = "disability";
+    this.m_name = _("Disability");
+    this.netname = "disability";
     this.m_skin = 7;
     this.m_color = '0.94 0.3 1';
 }
 BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY)
 
 REGISTER_BUFF(VENGEANCE) {
-    this.m_prettyName = _("Vengeance");
-    this.m_name = "vengeance";
+    this.m_name = _("Vengeance");
+    this.netname = "vengeance";
     this.m_skin = 15;
     this.m_color = '1 0.23 0.61';
 }
 BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE)
 
 REGISTER_BUFF(JUMP) {
-    this.m_prettyName = _("Jump");
-    this.m_name = "jump";
+    this.m_name = _("Jump");
+    this.netname = "jump";
     this.m_skin = 10;
     this.m_color = '0.24 0.78 1';
 }
@@ -92,8 +92,8 @@ BUFF_SPAWNFUNCS(jump, BUFF_JUMP)
 BUFF_SPAWNFUNC_Q3TA_COMPAT(jumper, BUFF_JUMP)
 
 REGISTER_BUFF(INVISIBLE) {
-    this.m_prettyName = _("Invisible");
-    this.m_name = "invisible";
+    this.m_name = _("Invisible");
+    this.netname = "invisible";
     this.m_skin = 12;
     this.m_color = '0.5 0.5 1';
 }
@@ -101,40 +101,40 @@ BUFF_SPAWNFUNCS(invisible, BUFF_INVISIBLE)
 BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE)
 
 REGISTER_BUFF(INFERNO) {
-    this.m_prettyName = _("Inferno");
-    this.m_name = "inferno";
+    this.m_name = _("Inferno");
+    this.netname = "inferno";
     this.m_skin = 16;
     this.m_color = '1 0.62 0';
 }
 BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO)
 
 REGISTER_BUFF(SWAPPER) {
-    this.m_prettyName = _("Swapper");
-    this.m_name = "swapper";
+    this.m_name = _("Swapper");
+    this.netname = "swapper";
     this.m_skin = 17;
     this.m_color = '0.63 0.36 1';
 }
 BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER)
 
 REGISTER_BUFF(MAGNET) {
-    this.m_prettyName = _("Magnet");
-    this.m_name = "magnet";
+    this.m_name = _("Magnet");
+    this.netname = "magnet";
     this.m_skin = 18;
     this.m_color = '1 0.95 0.18';
 }
 BUFF_SPAWNFUNCS(magnet, BUFF_MAGNET)
 
 REGISTER_BUFF(LUCK) {
-    this.m_prettyName = _("Luck");
-    this.m_name = "luck";
+    this.m_name = _("Luck");
+    this.netname = "luck";
     this.m_skin = 19;
     this.m_color = '1 0.23 0.44';
 }
 BUFF_SPAWNFUNCS(luck, BUFF_LUCK)
 
 REGISTER_BUFF(FLIGHT) {
-    this.m_prettyName = _("Flight");
-    this.m_name = "flight";
+    this.m_name = _("Flight");
+    this.netname = "flight";
     this.m_skin = 11;
     this.m_color = '0.23 0.44 1';
 }
index 8ef69aad982f45e49e646d960143f2afb5900d09..4a5dcc82aaa1ded918115799619f366f321b3d87 100644 (file)
@@ -3,7 +3,7 @@
 string BUFF_NAME(int i)
 {
     Buff b = Buffs_from(i);
-    return strcat(rgb_to_hexcolor(b.m_color), b.m_prettyName);
+    return strcat(rgb_to_hexcolor(b.m_color), b.m_name);
 }
 
 entity buff_FirstFromFlags(int _buffs)
index 35005e7a8fea65285d869afd82a8f247da833f78..7d4e583675b812169ee16ebc076b57f3973ee7e3 100644 (file)
@@ -20,13 +20,13 @@ REGISTRY_CHECK(Buffs)
 CLASS(Buff, Pickup)
        /** bit index */
        ATTRIB(Buff, m_itemid, int, 0);
-       ATTRIB(Buff, m_name, string, "buff");
+       ATTRIB(Buff, netname, string, "buff");
        ATTRIB(Buff, m_color, vector, '1 1 1');
-       ATTRIB(Buff, m_prettyName, string, "Buff");
+       ATTRIB(Buff, m_name, string, "Buff");
        ATTRIB(Buff, m_skin, int, 0);
        ATTRIB(Buff, m_sprite, string, "");
        METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
-               returns(this.m_prettyName, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.m_name));
+               returns(this.m_name, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.netname));
        }
 #ifdef SVQC
        METHOD(Buff, m_time, float(Buff this))
@@ -36,9 +36,8 @@ ENDCLASS(Buff)
 
 STATIC_INIT(REGISTER_BUFFS) {
     FOREACH(Buffs, true, {
-        it.netname = it.m_name; \
         it.m_itemid = BIT(it.m_id - 1); \
-        it.m_sprite = strzone(strcat("buff-", it.m_name)); \
+        it.m_sprite = strzone(strcat("buff-", it.netname)); \
     });
 }
 
index f751eecff091ff08e775bde798813d748fdb0b2f..790a10a86b8f16da4b5f0f0f65900d1813ca89a2 100644 (file)
@@ -5,7 +5,7 @@ MUTATOR_HOOKFUNCTION(cl_buffs, HUD_Powerups_add)
 {
     int allBuffs = STAT(BUFFS);
     FOREACH(Buffs, it.m_itemid & allBuffs, {
-               addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60);
+               addPowerupItem(it.m_name, strcat("buff_", it.netname), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60);
        });
 }
 MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format)
@@ -16,8 +16,8 @@ MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format)
     {
         Buff b = Buffs_from(this.wp_extra);
         M_ARGV(2, vector) = b.m_color;
-        M_ARGV(3, string) = b.m_prettyName;
-        M_ARGV(4, string) = strcat("buff_", b.m_name);
+        M_ARGV(3, string) = b.m_name;
+        M_ARGV(4, string) = strcat("buff_", b.netname);
         return true;
     }
 }
index 27f71b56c24ee02f88798ed51e214c15a5b05127..9338c986c9d6ca3e3cd571c51895697a113e0543 100644 (file)
@@ -210,7 +210,7 @@ float buff_Available(entity buff)
                return false;
        if (buff == BUFF_VAMPIRE && cvar("g_vampire"))
                return false;
-       return cvar(strcat("g_buffs_", buff.m_name));
+       return cvar(strcat("g_buffs_", buff.netname));
 }
 
 .int buff_seencount;
@@ -423,7 +423,7 @@ void buff_Medic_Heal(entity this)
 {
        FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range),
        {
-               if (!SAME_TEAM(it, this))
+               if (DIFF_TEAM(it, this))
                {
                        continue;
                }
index d9eacaae0c4e7a1cf2821d4574bb255a572c3bd5..0e0aa13fd0404412f13e612743d648bc1d436cca 100644 (file)
@@ -10,12 +10,12 @@ REGISTER_MUTATOR(damagetext, true);
 #define SV_DAMAGETEXT_ALL()             (autocvar_sv_damagetext >= 3)
 MUTATOR_HOOKFUNCTION(damagetext, PlayerDamaged) {
     if (SV_DAMAGETEXT_DISABLED()) return;
-    const entity attacker = M_ARGV(0, entity);
-    const entity hit = M_ARGV(1, entity); if (hit == attacker) return;
-    const float health = M_ARGV(2, float);
-    const float armor = M_ARGV(3, float);
-    const int deathtype = M_ARGV(5, int);
-    const float potential_damage = M_ARGV(6, float);
+    entity attacker = M_ARGV(0, entity);
+    entity hit = M_ARGV(1, entity); if (hit == attacker) return;
+    float health = M_ARGV(2, float);
+    float armor = M_ARGV(3, float);
+    int deathtype = M_ARGV(5, int);
+    float potential_damage = M_ARGV(6, float);
     if(DEATH_WEAPONOF(deathtype) == WEP_VAPORIZER) return;
     FOREACH_CLIENT(IS_REAL_CLIENT(it), {
         if (
index d5d3ba40f8ee233127078a6e4ff735d855f9f05a..4cfc0dd23779d4f5babee08a864b2fcce14dfdcd 100644 (file)
@@ -32,13 +32,13 @@ float DynamicHandicap_ClampHandicap(float handicap);
 void DynamicHandicap_UpdateHandicap()
 {
        float total_score = 0;
-       float total_players = 0;
+       float totalplayers = 0;
        FOREACH_CLIENT(IS_PLAYER(it),
        {
                total_score += PlayerScore_Get(it, SP_SCORE);
-               ++total_players;
+               ++totalplayers;
        });
-       float mean_score = total_score / total_players;
+       float mean_score = total_score / totalplayers;
        FOREACH_CLIENT(true,
        {
                float score = PlayerScore_Get(it, SP_SCORE);
index 3f8d087166353715a224acd0d7bbf4383cdb2154..42e3adbd73b74c6fa8eb1d23abfcde0c8e086145 100644 (file)
@@ -30,7 +30,7 @@ REGISTER_ITEM(VaporizerCells, Ammo) {
     this.m_sound                =   SND_VaporizerCells;
 #endif
     this.netname                =   "vaporizer_cells";
-    this.m_name                 =   "Vaporizer Ammo";
+    this.m_name                 =   _("Vaporizer ammo");
     this.m_icon                 =   "ammo_supercells";
 #ifdef SVQC
     this.m_botvalue             =   2000;
@@ -56,7 +56,7 @@ REGISTER_ITEM(ExtraLife, Powerup) {
     this.m_sound                =   SND_ExtraLife;
 #endif
     this.netname                =   "extralife";
-    this.m_name                 =   "Extra life";
+    this.m_name                 =   _("Extra life");
     this.m_icon                 =   "item_mega_health";
     this.m_color                =   '1 0 0';
     this.m_waypoint             =   _("Extra life");
@@ -88,7 +88,7 @@ REGISTER_ITEM(Invisibility, Powerup) {
     this.m_respawnsound     =   SND_STRENGTH_RESPAWN;
 #endif
     this.netname            =   "invisibility";
-    this.m_name             =   "Invisibility";
+    this.m_name             =   _("Invisibility");
     this.m_icon             =   "strength";
     this.m_color            =   '0 0 1';
     this.m_waypoint         =   _("Invisibility");
@@ -123,7 +123,7 @@ REGISTER_ITEM(Speed, Powerup) {
     this.m_respawnsound     =   SND_SHIELD_RESPAWN;
 #endif
     this.netname            =   "speed";
-    this.m_name             =   "Speed";
+    this.m_name             =   _("Speed");
     this.m_icon             =   "shield";
     this.m_color            =   '1 0 1';
     this.m_waypoint         =   _("Speed");
index 68a3af3baf76d5ae65f46819004a865c91ce604e..b4a3066fb04a17621faa46e165db9160320e9308 100644 (file)
@@ -383,7 +383,7 @@ void nade_ice_freeze(entity freezefield, entity frost_target, float freezetime)
 {
        frost_target.frozen_by = freezefield.realowner;
        Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1);
-       Freeze(frost_target, 1 / freezetime, 3, false);
+       Freeze(frost_target, 1 / freezetime, FROZEN_TEMP_DYING, false);
 
        Drop_Special_Items(frost_target);
 }
@@ -1021,7 +1021,7 @@ void nades_GiveBonus(entity player, float score)
        if (autocvar_g_nades_bonus)
        if (IS_REAL_CLIENT(player))
        if (IS_PLAYER(player) && STAT(NADE_BONUS, player) < autocvar_g_nades_bonus_max)
-       if (STAT(FROZEN, player) == 0)
+       if (!STAT(FROZEN, player))
        if (!IS_DEAD(player))
        {
                if ( STAT(NADE_BONUS_SCORE, player) < 1 )
@@ -1258,6 +1258,15 @@ MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST)
        }
 }
 
+#ifdef IS_REVIVING
+       #undef IS_REVIVING
+#endif
+
+// returns true if player is reviving it
+#define IS_REVIVING(player, it, revive_extra_size) \
+       (it != player && !STAT(FROZEN, it) && !IS_DEAD(it) && SAME_TEAM(it, player) \
+       && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+
 MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
 {
        entity player = M_ARGV(0, entity);
@@ -1329,43 +1338,43 @@ MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
        }
 
        int n = 0;
-       entity o = NULL;
+
+       IntrusiveList reviving_players = NULL;
+
        if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
                n = -1;
-       else if(STAT(FROZEN, player) == 3)
+       else if (STAT(FROZEN, player) == FROZEN_TEMP_DYING)
        {
                vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
                n = 0;
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
-                       if(!IS_DEAD(it) && STAT(FROZEN, it) == 0 && SAME_TEAM(it, player))
-                       if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
-                       {
-                               if(!o)
-                                       o = it;
-                               it.reviving = true;
-                               ++n;
-                       }
+               FOREACH_CLIENT(IS_PLAYER(it) && IS_REVIVING(player, it, revive_extra_size), {
+                       if (!reviving_players)
+                               reviving_players = IL_NEW();
+                       IL_PUSH(reviving_players, it);
+                       ++n;
                });
        }
 
-       if(n > 0 && STAT(FROZEN, player) == 3) // OK, there is at least one teammate reviving us
+       if (n > 0 && STAT(FROZEN, player) == FROZEN_TEMP_DYING) // OK, there is at least one teammate reviving us
        {
                STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
                SetResourceAmount(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * start_health));
 
                if(STAT(REVIVE_PROGRESS, player) >= 1)
                {
-                       Unfreeze(player);
+                       Unfreeze(player, false);
 
-                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
-                       Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
+                       entity first = IL_FIRST(reviving_players);
+                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, first.netname);
+                       Send_Notification(NOTIF_ONE, first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
                }
 
-               FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
+               IL_EACH(reviving_players, true, {
                        STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
-                       it.reviving = false;
                });
        }
+       if (reviving_players)
+               IL_DELETE(reviving_players);
 }
 
 MUTATOR_HOOKFUNCTION(nades, PlayerPhysics_UpdateStats)
@@ -1468,7 +1477,7 @@ MUTATOR_HOOKFUNCTION(nades, Damage_Calculate)
        if(autocvar_g_freezetag_revive_nade && STAT(FROZEN, frag_target) && frag_attacker == frag_target && frag_deathtype == DEATH_NADE.m_id)
        if(time - frag_inflictor.toss_time <= 0.1)
        {
-               Unfreeze(frag_target);
+               Unfreeze(frag_target, false);
                SetResourceAmount(frag_target, RESOURCE_HEALTH, autocvar_g_freezetag_revive_nade_health);
                Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
                M_ARGV(4, float) = 0;
index d278ca9d444b052f4a2a9a3151321be48b78460f..688928ce1425b23a4a1294de87788d66b1406b07 100644 (file)
@@ -36,7 +36,7 @@ void W_OverkillHeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity
        }
 
        float okhmg_spread = bound(WEP_CVAR_PRI(okhmg, spread_min), WEP_CVAR_PRI(okhmg, spread_min) + (WEP_CVAR_PRI(okhmg, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR_PRI(okhmg, spread_max));
-       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okhmg_spread, WEP_CVAR_PRI(okhmg, solidpenetration), WEP_CVAR_PRI(okhmg, damage), WEP_CVAR_PRI(okhmg, force), WEP_OVERKILL_HMG.m_id, 0);
+       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okhmg_spread, WEP_CVAR_PRI(okhmg, solidpenetration), WEP_CVAR_PRI(okhmg, damage), WEP_CVAR_PRI(okhmg, force), WEP_OVERKILL_HMG.m_id, EFFECT_RIFLE);
 
        actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
 
@@ -51,8 +51,7 @@ void W_OverkillHeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity
                SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
        }
 
-       int slot = weaponslot(weaponentity);
-       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(okhmg, refire) * W_WeaponRateFactor(actor);
+       ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(okhmg, refire) * W_WeaponRateFactor(actor);
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okhmg, refire), W_OverkillHeavyMachineGun_Attack_Auto);
 }
 
index 63c1e245b4ad1121cfbf593ac4e563da67c6b2b0..aa872f1541a64f052d79d7c73751a6c7e46a984c 100644 (file)
@@ -30,7 +30,7 @@ void W_OverkillMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weap
        }
 
        okmachinegun_spread = bound(WEP_CVAR_PRI(okmachinegun, spread_min), WEP_CVAR_PRI(okmachinegun, spread_min) + (WEP_CVAR_PRI(okmachinegun, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR_PRI(okmachinegun, spread_max));
-       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okmachinegun_spread, WEP_CVAR_PRI(okmachinegun, solidpenetration), WEP_CVAR_PRI(okmachinegun, damage), WEP_CVAR_PRI(okmachinegun, force), WEP_OVERKILL_MACHINEGUN.m_id, 0);
+       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okmachinegun_spread, WEP_CVAR_PRI(okmachinegun, solidpenetration), WEP_CVAR_PRI(okmachinegun, damage), WEP_CVAR_PRI(okmachinegun, force), WEP_OVERKILL_MACHINEGUN.m_id, EFFECT_RIFLE);
 
        actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
 
@@ -45,8 +45,7 @@ void W_OverkillMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weap
                SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
        }
 
-       int slot = weaponslot(weaponentity);
-       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(okmachinegun, refire) * W_WeaponRateFactor(actor);
+       ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(okmachinegun, refire) * W_WeaponRateFactor(actor);
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okmachinegun, refire), W_OverkillMachineGun_Attack_Auto);
 }
 
@@ -153,4 +152,3 @@ METHOD(OverkillMachineGun, wr_impacteffect, void(entity thiswep, entity actor))
 }
 
 #endif
-
index 997b49de9d6161aab5b74374d8498f790b6107e3..32176da90a3b1713080854ec37508d1700a75b7f 100644 (file)
@@ -4,7 +4,7 @@ CLASS(OverkillMachineGun, Weapon)
 /* spawnfunc */ ATTRIB(OverkillMachineGun, m_canonical_spawnfunc, string, "weapon_okmachinegun");
 /* ammotype  */ ATTRIB(OverkillMachineGun, ammo_type, int, RESOURCE_BULLETS);
 /* impulse   */ ATTRIB(OverkillMachineGun, impulse, int, 3);
-/* flags        */ ATTRIB(OverkillMachineGun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_MUTATORBLOCKED);
+/* flags        */ ATTRIB(OverkillMachineGun, spawnflags, int, WEP_FLAG_HIDDEN | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_MUTATORBLOCKED);
 /* rating      */ ATTRIB(OverkillMachineGun, bot_pickupbasevalue, float, 7000);
 /* color        */ ATTRIB(OverkillMachineGun, wpcolor, vector, '1 1 0');
 /* modelname */ ATTRIB(OverkillMachineGun, mdl, string, "ok_mg");
index 0f67ddba2b0e8bca1318e35c7f75461cff7ae9da..4fbd200b699321160defd98d9259311ec8de60a2 100644 (file)
@@ -82,7 +82,7 @@ void W_OverkillNex_Attack(Weapon thiswep, entity actor, .entity weaponentity, fl
        mydmg *= charge;
        myforce *= charge;
 
-       W_SetupShot(actor, weaponentity, true, 5, SND_NEXFIRE, CH_WEAPON_A, mydmg, WEP_OVERKILL_NEX.m_id);
+       W_SetupShot(actor, weaponentity, true, 5, SND_NEXFIRE, CH_WEAPON_A, mydmg, thiswep.m_id);
        if(charge > WEP_CVAR(oknex, charge_animlimit) && WEP_CVAR(oknex, charge_animlimit)) // if the OverkillNex is overcharged, we play an extra sound
        {
                sound(actor, CH_WEAPON_B, SND_NEXCHARGE, VOL_BASE * (charge - 0.5 * WEP_CVAR(oknex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(oknex, charge_animlimit)), ATTN_NORM);
@@ -90,7 +90,7 @@ void W_OverkillNex_Attack(Weapon thiswep, entity actor, .entity weaponentity, fl
 
        yoda = 0;
        damage_goodhits = 0;
-       FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_OVERKILL_NEX.m_id);
+       FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, thiswep.m_id);
 
        if(yoda && flying)
                Send_Notification(NOTIF_ONE, actor, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
@@ -269,7 +269,7 @@ METHOD(OverkillNex, wr_setup, void(entity thiswep, entity actor, .entity weapone
 METHOD(OverkillNex, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
        float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(oknex, ammo);
-       ammo_amount += (autocvar_g_balance_oknex_reload_ammo && actor.(weaponentity).(weapon_load[WEP_OVERKILL_NEX.m_id]) >= WEP_CVAR_PRI(oknex, ammo));
+       ammo_amount += (autocvar_g_balance_oknex_reload_ammo && actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(oknex, ammo));
        return ammo_amount;
 }
 
@@ -279,7 +279,7 @@ METHOD(OverkillNex, wr_checkammo2, bool(entity thiswep, entity actor, .entity we
        {
                // don't allow charging if we don't have enough ammo
                float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(oknex, ammo);
-               ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_NEX.m_id]) >= WEP_CVAR_SEC(oknex, ammo);
+               ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(oknex, ammo);
                return ammo_amount;
        }
        else
index f38588e39e6a9a2ef11804b6edea7711e821d30e..ec8ceeb3902df4ed2cbdc7affa69d654cc9448c8 100644 (file)
@@ -4,7 +4,7 @@ CLASS(OverkillNex, Weapon)
 /* spawnfunc */ ATTRIB(OverkillNex, m_canonical_spawnfunc, string, "weapon_oknex");
 /* ammotype  */ ATTRIB(OverkillNex, ammo_type, int, RESOURCE_CELLS);
 /* impulse   */ ATTRIB(OverkillNex, impulse, int, 7);
-/* flags     */ ATTRIB(OverkillNex, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
+/* flags     */ ATTRIB(OverkillNex, spawnflags, int, WEP_FLAG_HIDDEN | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
 /* rating    */ ATTRIB(OverkillNex, bot_pickupbasevalue, float, 8000);
 /* color     */ ATTRIB(OverkillNex, wpcolor, vector, '0.5 1 1');
 /* modelname */ ATTRIB(OverkillNex, mdl, string, "ok_sniper");
index 3174fa7ff31ee4006231c4f45fbba5395619fe74..50c842d9844144c6c5da0b60ed31f41c9c7cd33b 100644 (file)
@@ -14,7 +14,7 @@ void W_OverkillRocketPropelledChainsaw_Explode(entity this, entity directhitenti
        {
                // if chainsaw hit something, it removed fired damage (so that direct hit is 100%)
                // now that we also damaged something by explosion we'd go over 100% so let's add the fired damage back
-               accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype).m_id, WEP_CVAR(okrpc, damage), 0);
+               accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype), WEP_CVAR(okrpc, damage), 0);
        }
 
        delete(this);
@@ -71,7 +71,7 @@ void W_OverkillRocketPropelledChainsaw_Think(entity this)
                                // We remove it here so that a direct hit that passes through and doesn't damage anything by the explosion later is still 100%.
                                float fired_damage = WEP_CVAR_PRI(okrpc, damage2) - WEP_CVAR_PRI(okrpc, damage);
                                float hit_damage = WEP_CVAR_PRI(okrpc, damage2);
-                               accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype).m_id, fired_damage, hit_damage);
+                               accuracy_add(this.realowner, DEATH_WEAPONOF(this.projectiledeathtype), fired_damage, hit_damage);
                        }
                        this.m_chainsaw_damage += WEP_CVAR_PRI(okrpc, damage2);
                }
@@ -84,13 +84,13 @@ void W_OverkillRocketPropelledChainsaw_Think(entity this)
        this.nextthink = time;
 }
 
-void W_OverkillRocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .entity weaponentity)
+void W_OverkillRocketPropelledChainsaw_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 {
        entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(actor);
        entity flash = spawn ();
 
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(okrpc, ammo), weaponentity);
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(okrpc, damage), WEP_OVERKILL_RPC.m_id);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(okrpc, damage), thiswep.m_id);
        Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
        PROJECTILE_MAKETRIGGER(missile);
 
@@ -106,7 +106,7 @@ void W_OverkillRocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .en
        IL_PUSH(g_damagedbycontents, missile);
        set_movetype(missile, MOVETYPE_FLY);
 
-       missile.projectiledeathtype = WEP_OVERKILL_RPC.m_id;
+       missile.projectiledeathtype = thiswep.m_id;
        missile.weaponentity_fld = weaponentity;
        setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
 
@@ -192,14 +192,14 @@ METHOD(OverkillRocketPropelledChainsaw, wr_think, void(entity thiswep, entity ac
 METHOD(OverkillRocketPropelledChainsaw, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
        float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(okrpc, ammo);
-       ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_RPC.m_id]) >= WEP_CVAR_PRI(okrpc, ammo);
+       ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(okrpc, ammo);
        return ammo_amount;
 }
 
 METHOD(OverkillRocketPropelledChainsaw, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
        float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(okrpc, ammo);
-       ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_RPC.m_id]) >= WEP_CVAR_SEC(okrpc, ammo);
+       ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(okrpc, ammo);
        return ammo_amount;
 }
 
index 2f1651776fdab25a67b98a42b0f9e386b44805ea..2461ba0cb09dd1098019bd1929a08fccbf6accbb 100644 (file)
@@ -45,7 +45,7 @@ METHOD(OverkillShotgun, wr_think, void(entity thiswep, entity actor, .entity wea
        }
        if (fire & 1) // Primary attack
        {
-               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(okshotgun, animtime)))
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(okshotgun, refire)))
                {
                        return;
                }
@@ -55,7 +55,8 @@ METHOD(OverkillShotgun, wr_think, void(entity thiswep, entity actor, .entity wea
                        WEP_CVAR_PRI(okshotgun, bullets),
                        WEP_CVAR_PRI(okshotgun, spread),
                        WEP_CVAR_PRI(okshotgun, solidpenetration),
-                       WEP_CVAR_PRI(okshotgun, force));
+                       WEP_CVAR_PRI(okshotgun, force),
+                       EFFECT_RIFLE_WEAK);
                weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okshotgun, animtime), w_ready);
                return;
        }
index a383c9d7a8b1bef15311879e438bd60ccc9c0649..1124baa68bff28fc72e75c9c4d4d2529384d3e7a 100644 (file)
@@ -4,7 +4,7 @@ CLASS(OverkillShotgun, Weapon)
 /* spawnfunc */ ATTRIB(OverkillShotgun, m_canonical_spawnfunc, string, "weapon_okshotgun");
 /* ammotype  */ ATTRIB(OverkillShotgun, ammo_type, int, RESOURCE_SHELLS);
 /* impulse   */ ATTRIB(OverkillShotgun, impulse, int, 2);
-/* flags     */ ATTRIB(OverkillShotgun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
+/* flags     */ ATTRIB(OverkillShotgun, spawnflags, int, WEP_FLAG_HIDDEN | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED);
 /* rating    */ ATTRIB(OverkillShotgun, bot_pickupbasevalue, float, 6000);
 /* color     */ ATTRIB(OverkillShotgun, wpcolor, vector, '0.5 0.25 0');
 /* modelname */ ATTRIB(OverkillShotgun, mdl, string, "ok_shotgun");
index d80b21d5a6e0cec3b4a59f5c403b948eece5cd85..9458189fd0ed20a448b9da906b3f654c12632452 100644 (file)
@@ -312,8 +312,9 @@ string sandbox_ObjectPort_Save(entity e, bool database)
 entity sandbox_ObjectPort_Load(entity this, string s, float database)
 {
        // load object properties, and spawn a new object with them
-       float n, i;
+       int n, i;
        entity e = NULL, parent = NULL;
+       string arg = string_null;
 
        // separate objects between the ; symbols
        n = tokenizebyseparator(s, "; ");
@@ -323,9 +324,10 @@ entity sandbox_ObjectPort_Load(entity this, string s, float database)
        // now separate and apply the properties of each object
        for(i = 0; i < n; ++i)
        {
-               float argv_num;
+               #define SANDBOX_GETARG arg = argv(++argv_num);
+               int argv_num = -1; // starts at -1 so I don't need postincrement
+
                string tagname = string_null;
-               argv_num = 0;
                tokenize_console(port_string[i]);
                e = sandbox_ObjectSpawn(this, database);
 
@@ -333,38 +335,40 @@ entity sandbox_ObjectPort_Load(entity this, string s, float database)
                if(i)
                {
                        // properties stored only for child objects
-                       if(argv(argv_num) != "")        tagname = argv(argv_num);       else tagname = string_null;     ++argv_num;
+                       SANDBOX_GETARG; tagname = (arg != "") ? arg : string_null;
                }
                else
                {
                        // properties stored only for parent objects
                        if(database)
                        {
-                               setorigin(e, stov(argv(argv_num)));     ++argv_num;
-                               e.angles = stov(argv(argv_num));        ++argv_num;
+                               SANDBOX_GETARG; setorigin(e, stov(arg));
+                               SANDBOX_GETARG; e.angles = stov(arg);
                        }
                        parent = e; // mark parent objects as such
                }
                // properties stored for all objects
-               _setmodel(e, argv(argv_num));   ++argv_num;
-               e.skin = stof(argv(argv_num));  ++argv_num;
-               e.alpha = stof(argv(argv_num)); ++argv_num;
-               e.colormod = stov(argv(argv_num));      ++argv_num;
-               e.glowmod = stov(argv(argv_num));       ++argv_num;
-               e.frame = stof(argv(argv_num)); ++argv_num;
-               sandbox_ObjectEdit_Scale(e, stof(argv(argv_num)));      ++argv_num;
-               e.solid = e.old_solid = stof(argv(argv_num));   ++argv_num;
-               e.old_movetype = stof(argv(argv_num));  ++argv_num;
+               SANDBOX_GETARG; _setmodel(e, arg);
+               SANDBOX_GETARG; e.skin = stof(arg);
+               SANDBOX_GETARG; e.alpha = stof(arg);
+               SANDBOX_GETARG; e.colormod = stov(arg);
+               SANDBOX_GETARG; e.glowmod = stov(arg);
+               SANDBOX_GETARG; e.frame = stof(arg);
+               SANDBOX_GETARG; sandbox_ObjectEdit_Scale(e, stof(arg));
+               SANDBOX_GETARG; e.solid = e.old_solid = stof(arg);
+               SANDBOX_GETARG; e.old_movetype = stof(arg);
                set_movetype(e, e.old_movetype);
-               e.damageforcescale = stof(argv(argv_num));      ++argv_num;
-               strfree(e.material);    if(argv(argv_num) != "")        e.material = strzone(argv(argv_num));   else    e.material = string_null;       ++argv_num;
+               SANDBOX_GETARG; e.damageforcescale = stof(arg);
+               strfree(e.material);
+               SANDBOX_GETARG; e.material = (arg != "") ? strzone(arg) : string_null;
                if(database)
                {
                        // properties stored only for the database
-                       strfree(e.crypto_idfp); if(argv(argv_num) != "")        e.crypto_idfp = strzone(argv(argv_num));        else    e.crypto_idfp = string_null;    ++argv_num;
-                       strcpy(e.netname, argv(argv_num));      ++argv_num;
-                       strcpy(e.message, argv(argv_num));      ++argv_num;
-                       strcpy(e.message2, argv(argv_num));     ++argv_num;
+                       strfree(e.crypto_idfp);
+                       SANDBOX_GETARG; e.crypto_idfp = (arg != "") ? strzone(arg) : string_null;
+                       SANDBOX_GETARG; strcpy(e.netname, arg);
+                       SANDBOX_GETARG; strcpy(e.message, arg);
+                       SANDBOX_GETARG; strcpy(e.message2, arg);
                }
 
                // attach last
index ee2a5be7f504d11c376bd85b31ec28b505b7eeec..fdcc4beee5fb51ab89c4ca8774638aa435241d14 100644 (file)
@@ -90,7 +90,7 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
                        if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_max && tested >= autocvar_g_spawn_near_teammate_ignore_spawnpoint_max) break;
 
                        if (PHYS_INPUT_BUTTON_CHAT(it)) continue;
-                       if (!SAME_TEAM(player, it)) continue;
+                       if (DIFF_TEAM(player, it)) continue;
                        if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && GetResourceAmount(it, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable) continue;
                        if (IS_DEAD(it)) continue;
                        if (time < it.msnt_timer) continue;
index 115e6ca9109341fcaa4c61974eaafe416b342f15..e74cfb1152844c485c94e9f72705d7d004f46e54 100644 (file)
@@ -28,7 +28,9 @@ MUTATOR_HOOKFUNCTION(vh, GrappleHookThink)
                thehook.owner.damage_dealt += autocvar_g_vampirehook_damage;
                Damage(dmgent, thehook, thehook.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, DMG_NOWEP, thehook.origin, '0 0 0');
                entity targ = ((SAME_TEAM(thehook.owner, thehook.aiment)) ? thehook.aiment : thehook.owner);
-               Heal(targ, thehook.owner, autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+               // TODO: we can't do this due to an issue with globals and the mutator arguments
+               //Heal(targ, thehook.owner, autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+               SetResourceAmountExplicit(targ, RESOURCE_HEALTH, min(GetResourceAmount(targ, RESOURCE_HEALTH) + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max));
 
                if(dmgent == thehook.owner)
                        TakeResource(dmgent, RESOURCE_HEALTH, autocvar_g_vampirehook_damage); // FIXME: friendly fire?!
index b527bdc1a84b020088603478e4007db063ce5c75..c8c4db546a71267d4bb86f7e71e0239ba1c7f629 100644 (file)
@@ -14,8 +14,8 @@ REGISTER_WAYPOINT(RaceFinish, _("Finish"), "", '1 0.5 0', 1);
 REGISTER_WAYPOINT(RaceStart, _("Start"), "", '1 0.5 0', 1);
 REGISTER_WAYPOINT(RaceStartFinish, _("Start"), "", '1 0.5 0', 1);
 
-REGISTER_WAYPOINT(AssaultDefend, _("Defend"), "", '1 0.5 0', 1);
-REGISTER_WAYPOINT(AssaultDestroy, _("Destroy"), "", '1 0.5 0', 1);
+REGISTER_WAYPOINT(AssaultDefend, _("Defend"), "as_defend", '1 0.5 0', 1);
+REGISTER_WAYPOINT(AssaultDestroy, _("Destroy"), "as_destroy", '1 0.5 0', 1);
 REGISTER_WAYPOINT(AssaultPush, _("Push"), "", '1 0.5 0', 1);
 
 REGISTER_WAYPOINT(FlagCarrier, _("Flag carrier"), "", '0.8 0.8 0', 1);
index dcbb65f65cd29472659141b93617b7a8182c2075..66904d0070bd879fd151e14be11df995600a3e43 100644 (file)
@@ -552,11 +552,13 @@ void Draw_WaypointSprite(entity this)
         LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
     }
 
-    if (time - floor(time) > 0.5)
+    float health_val = GetResourceAmount(this, RESOURCE_HEALTH);
+    float blink_time = (health_val >= 0) ? (health_val * 10) : time;
+    if (blink_time - floor(blink_time) > 0.5)
     {
         if (this.helpme && time < this.helpme)
             a *= SPRITE_HELPME_BLINK;
-        else if (this.lifetime > 0) // fading out waypoints don't blink
+        else if (!this.lifetime) // fading out waypoints don't blink
             a *= spritelookupblinkvalue(this, spriteimage);
     }
 
@@ -590,7 +592,7 @@ void Draw_WaypointSprite(entity this)
             ang += M_PI;
 
                float f1 = d.x / vid_conwidth;
-               float f2 = d.y / vid_conheight; 
+               float f2 = d.y / vid_conheight;
                if (f1 == 0) { f1 = 0.000001; }
                if (f2 == 0) { f2 = 0.000001; }
 
@@ -1101,7 +1103,7 @@ entity WaypointSprite_SpawnFixed(
 
 entity WaypointSprite_DeployFixed(
     entity spr,
-    float limited_range,
+    bool limited_range,
     entity player,
     vector ofs,
     entity icon // initial icon
@@ -1133,7 +1135,7 @@ entity WaypointSprite_DeployPersonal(
 entity WaypointSprite_Attach(
     entity spr,
     entity player,
-    float limited_range,
+    bool limited_range,
     entity icon // initial icon
 )
 {
index 94d735a1f6096b2f3a77a0b712e4124912bb0aa3..e2190b66a26a9cef1c0efb477c51d53c7276422c 100644 (file)
@@ -52,6 +52,7 @@ int autocvar_g_waypointsprite_spam;
 float autocvar_g_waypointsprite_timealphaexponent;
 bool autocvar_g_waypointsprite_turrets = true;
 float autocvar_g_waypointsprite_turrets_maxdist = 5000;
+bool autocvar_g_waypointsprite_turrets_text = false;
 bool autocvar_g_waypointsprite_uppercase;
 bool autocvar_g_waypointsprite_text;
 float autocvar_g_waypointsprite_iconsize = 32;
@@ -203,7 +204,7 @@ entity WaypointSprite_SpawnFixed(
 .entity waypointsprite_deployed_fixed;
 entity WaypointSprite_DeployFixed(
     entity spr,
-    float limited_range,
+    bool limited_range,
     entity player,
     vector ofs,
     entity icon // initial icon
@@ -222,7 +223,7 @@ entity WaypointSprite_DeployPersonal(
 entity WaypointSprite_Attach(
     entity spr,
     entity player,
-    float limited_range,
+    bool limited_range,
     entity icon // initial icon
 );
 
index a15ef80d1e7f8feece7b0dc8f7927e3962d50db0..1b93cbf35986f4e62cdb127486095f8cd5f55b89 100644 (file)
     MSG_INFO_NOTIF(ITEM_BUFF_GOT,                           N_CONSOLE,  0, 1, "item_buffname", "",          "",     _("^BGYou got the %s^BG buff!"), "")
 
     MSG_INFO_NOTIF(ITEM_WEAPON_DONTHAVE,                    N_DISABLE,  0, 1, "item_wepname", "",                           "",     _("^BGYou do not have the ^F1%s"), "")
-    MSG_INFO_NOTIF(ITEM_WEAPON_DROP,                        N_DISABLE,  1, 1, "item_wepname item_wepammo", "",              "",     _("^BGYou dropped the ^F1%s^BG%s"), "")
+    MSG_INFO_NOTIF(ITEM_WEAPON_DROP,                        N_DISABLE,  0, 2, "item_wepname item_wepammo", "",              "",     _("^BGYou dropped the ^F1%s^BG%s"), "")
     MSG_INFO_NOTIF(ITEM_WEAPON_GOT,                         N_DISABLE,  0, 1, "item_wepname", "",                           "",     _("^BGYou got the ^F1%s"), "")
     MSG_INFO_NOTIF(ITEM_WEAPON_NOAMMO,                      N_DISABLE,  0, 1, "item_wepname", "",                           "",     _("^BGYou don't have enough ammo for the ^F1%s"), "")
     MSG_INFO_NOTIF(ITEM_WEAPON_PRIMORSEC,                   N_DISABLE,  0, 3, "item_wepname f2primsec f3primsec", "",       "",     _("^F1%s %s^BG is unable to fire, but its ^F1%s^BG can"), "")
 
     MSG_INFO_NOTIF(CONNECTING,                              N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^BG is connecting..."), "")
     MSG_INFO_NOTIF(JOIN_CONNECT,                            N_CHATCON,  1, 0, "s1", "",         "",     _("^BG%s^F3 connected"), "")
-    MULTITEAM_INFO(JOIN_CONNECT_TEAM, 4,                    N_CHATCON,  1, 0, "s1", "",         "",     _("^BG%s^F3 connected and joined the ^TC^TT team"), "", NAME)
     MSG_INFO_NOTIF(JOIN_PLAY,                               N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^F3 is now playing"), "")
     MULTITEAM_INFO(JOIN_PLAY_TEAM, 4,                       N_CHATCON,  1, 0, "s1", "",         "",     _("^BG%s^F3 is now playing on the ^TC^TT team"), "", NAME)
 
     MSG_INFO_NOTIF(QUIT_KICK_IDLING,                        N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for idling"), "")
     MSG_INFO_NOTIF(QUIT_KICK_SPECTATING,                    N_CONSOLE,  0, 0, "", "",           "",             _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "")
     MSG_INFO_NOTIF(QUIT_KICK_TEAMKILL,                      N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for excessive teamkilling"), "")
-    MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CONSOLE,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now spectating"), "")
+    MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now spectating"), "")
 
     MSG_INFO_NOTIF(RACE_ABANDONED,                          N_CONSOLE,  1, 0, "s1", "",                                                                     "",                         _("^BG%s^BG has abandoned the race"), "")
     MSG_INFO_NOTIF(RACE_FAIL_RANKED,                        N_CONSOLE,  1, 3, "s1 race_col f1ord race_col f3race_time race_diff", "s1 f3race_time",         "race_newfail",             _("^BG%s^BG couldn't break their %s%s^BG place record of %s%s %s"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou cannot change to a larger team"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou are not allowed to change teams"), "")
 
-    MSG_INFO_NOTIF(VERSION_BETA,                            N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
+    MSG_INFO_NOTIF(VERSION_BETA,                            N_CONSOLE,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
     MSG_INFO_NOTIF(VERSION_OLD,                             N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s^BG, you have ^F2Xonotic %s"), "")
     MSG_INFO_NOTIF(VERSION_OUTDATED,                        N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^F1Xonotic %s^BG is out, and you still have ^F2Xonotic %s^BG - get the update from ^F3http://www.xonotic.org/^BG!"), "")
 
     MSG_CENTER_NOTIF(ITEM_FUELREGEN_GOT,                N_ENABLE,    0, 0, "",                                   CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Fuel regenerator"), "")
     MSG_CENTER_NOTIF(ITEM_JETPACK_GOT,                  N_ENABLE,    0, 0, "",                                   CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Jetpack"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_DONTHAVE,              N_ENABLE,    0, 1, "item_wepname",                       CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "")
-    MSG_CENTER_NOTIF(ITEM_WEAPON_DROP,                  N_ENABLE,    1, 1, "item_wepname item_wepammo",          CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "")
+    MSG_CENTER_NOTIF(ITEM_WEAPON_DROP,                  N_ENABLE,    0, 2, "item_wepname item_wepammo",          CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_GOT,                   N_ENABLE,    0, 1, "item_wepname",                       CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_NOAMMO,                N_ENABLE,    0, 1, "item_wepname",                       CPID_ITEM, "item_centime 0", _("^BGYou don't have enough ammo for the ^F1%s"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_PRIMORSEC,             N_ENABLE,    0, 3, "item_wepname f2primsec f3primsec",   CPID_ITEM, "item_centime 0", _("^F1%s %s^BG is unable to fire, but its ^F1%s^BG can"), "")
 #define A_ALWAYS 2
 
 // MSG_CHOICE_NOTIFICATIONS
-    MULTITEAM_CHOICE(CTF_CAPTURE_BROKEN, 4,     N__NORMAL, A_ALWAYS,  MSG_INFO,   INFO_CTF_CAPTURE,                   INFO_CTF_CAPTURE_BROKEN)
-    MULTITEAM_CHOICE(CTF_CAPTURE_TIME, 4,       N__NORMAL, A_ALWAYS,  MSG_INFO,   INFO_CTF_CAPTURE,                   INFO_CTF_CAPTURE_TIME)
-    MULTITEAM_CHOICE(CTF_CAPTURE_UNBROKEN, 4,   N__NORMAL, A_ALWAYS,  MSG_INFO,   INFO_CTF_CAPTURE,                   INFO_CTF_CAPTURE_UNBROKEN)
+    MULTITEAM_CHOICE(CTF_CAPTURE_BROKEN, 4,     N_VERBOSE, A_ALWAYS,  MSG_INFO,   INFO_CTF_CAPTURE,                   INFO_CTF_CAPTURE_BROKEN)
+    MULTITEAM_CHOICE(CTF_CAPTURE_TIME, 4,       N_VERBOSE, A_ALWAYS,  MSG_INFO,   INFO_CTF_CAPTURE,                   INFO_CTF_CAPTURE_TIME)
+    MULTITEAM_CHOICE(CTF_CAPTURE_UNBROKEN, 4,   N_VERBOSE, A_ALWAYS,  MSG_INFO,   INFO_CTF_CAPTURE,                   INFO_CTF_CAPTURE_UNBROKEN)
     MULTITEAM_CHOICE(CTF_PICKUP_TEAM, 4,        N__NORMAL, A_ALWAYS,  MSG_CENTER, CENTER_CTF_PICKUP_TEAM,             CENTER_CTF_PICKUP_TEAM_VERBOSE)
     MSG_CHOICE_NOTIF(CTF_PICKUP_TEAM_NEUTRAL,   N__NORMAL, A_ALWAYS,  MSG_CENTER, CENTER_CTF_PICKUP_TEAM_NEUTRAL,     CENTER_CTF_PICKUP_TEAM_VERBOSE_NEUTRAL)
     MSG_CHOICE_NOTIF(CTF_PICKUP_ENEMY,          N__NORMAL, A_ALWAYS,  MSG_CENTER, CENTER_CTF_PICKUP_ENEMY,            CENTER_CTF_PICKUP_ENEMY_VERBOSE)
index 7982ee01f73acfc3348bed12334f125d60af494e..6cbf91cc9b3fe185cb1b728b75dc8d273435e612 100644 (file)
@@ -6,6 +6,7 @@
 #include <common/teams.qh>
 #include <common/util.qh>
 #include <common/sounds/sound.qh>
+#include <common/weapons/all.qh>
 
 #ifdef CSQC
 #include <client/autocvars.qh>
@@ -370,7 +371,7 @@ float autocvar_notification_show_sprees_center_specialonly = true;
        spree_end: placed at the end of murder messages to show ending of sprees
        spree_lost: placed at the end of suicide messages to show losing of sprees
        item_wepname: return full name of a weapon from weaponid
-       item_wepammo: ammo display for weapon from string
+       item_wepammo: ammo display for weapon from f1 and f2
        item_centime: amount of time to display weapon message in centerprint
        item_buffname: return full name of a buff from buffid
        death_team: show the full name of the team a player is switching from
@@ -433,7 +434,7 @@ string BUFF_NAME(int i);
        ARG_CASE(ARG_CS_SV,     "item_wepname",  Weapons_from(f1).m_name) \
        ARG_CASE(ARG_CS_SV,     "item_buffname", BUFF_NAME(f1)) \
        ARG_CASE(ARG_CS_SV,     "f3buffname",    BUFF_NAME(f3)) \
-       ARG_CASE(ARG_CS_SV,     "item_wepammo",  (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \
+       ARG_CASE(ARG_CS_SV,     "item_wepammo",  (f2 > 0 ? notif_arg_item_wepammo(f1, f2) : "")) \
        ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
        ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
        ARG_CASE(ARG_CS,        "death_team",    Team_ColoredFullName(f1 - 1)) \
@@ -624,6 +625,23 @@ string notif_arg_spree_inf(float type, string input, string player, float spree)
        return "";
 }
 
+string notif_arg_item_wepammo(float f1, float f2)
+{
+       string ammoitems = "";
+       Weapon wep = Weapons_from(f1);
+       switch (wep.ammo_type)
+       {
+               case RESOURCE_SHELLS:  ammoitems = ITEM_Shells.m_name;      break;
+               case RESOURCE_BULLETS: ammoitems = ITEM_Bullets.m_name;     break;
+               case RESOURCE_ROCKETS: ammoitems = ITEM_Rockets.m_name;     break;
+               case RESOURCE_CELLS:   ammoitems = ITEM_Cells.m_name;       break;
+               case RESOURCE_PLASMA:  ammoitems = ITEM_Plasma.m_name;      break;
+               case RESOURCE_FUEL:    ammoitems = ITEM_JetpackFuel.m_name; break;
+               default: return ""; // doesn't use ammo
+       }
+       return sprintf(_(" with %d %s"), f2, ammoitems);
+}
+
 
 // ====================================
 //  Initialization/Create Declarations
index 9ac3e4299901db21051d4ea2f206fe8636bbfa30..e31b4076beca1072f2e1870f61fa0b73ec6800da 100644 (file)
@@ -232,6 +232,9 @@ int _Movetype_FlyMove(entity this, float dt, bool applygravity, vector stepnorma
        if(GAMEPLAYFIX_EASIERWATERJUMP(this) && (this.flags & FL_WATERJUMP) && !(blocked & 8))
                this.velocity = primal_velocity;
 
+       if(PHYS_WALLCLIP(this) && this.pm_time && !(this.flags & FL_WATERJUMP) && !(blocked & 8))
+               this.velocity = primal_velocity;
+
        if(applygravity)
        {
                if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !IS_ONGROUND(this))
@@ -403,11 +406,11 @@ entity _Movetype_TestEntityPosition_ent;
 bool _Movetype_TestEntityPosition(vector ofs)  // SV_TestEntityPosition
 {
     entity this = _Movetype_TestEntityPosition_ent;
-//     vector org = this.origin + ofs;
+       vector org = this.origin + ofs;
 
        int cont = this.dphitcontentsmask;
        this.dphitcontentsmask = DPCONTENTS_SOLID;
-       tracebox(this.origin, this.mins, this.maxs, this.origin, ((this.move_movetype == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), this);
+       tracebox(org, this.mins, this.maxs, org, ((this.move_movetype == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), this);
        this.dphitcontentsmask = cont;
 
        if(trace_startsolid)
@@ -418,11 +421,11 @@ bool _Movetype_TestEntityPosition(vector ofs)  // SV_TestEntityPosition
        return false;
 }
 
-bool _Movetype_UnstickEntity(entity this)  // SV_UnstickEntity
+int _Movetype_UnstickEntity(entity this)  // SV_UnstickEntity
 {
     _Movetype_TestEntityPosition_ent = this;
        if (!_Movetype_TestEntityPosition(' 0  0  0')) {
-           return true;
+           return UNSTICK_FINE;
        }
        #define X(v) if (_Movetype_TestEntityPosition(v))
        X('-1  0  0') X(' 1  0  0')
@@ -441,13 +444,32 @@ bool _Movetype_UnstickEntity(entity this)  // SV_UnstickEntity
         {
             LOG_DEBUGF("Can't unstick an entity (edict: %d, classname: %s, origin: %s)",
                 etof(this), this.classname, vtos(this.origin));
-            return false;
+            return UNSTICK_STUCK;
         }
        }
        LOG_DEBUGF("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)",
                etof(this), this.classname, vtos(this.origin));
        _Movetype_LinkEdict(this, true);
-       return true;
+       return UNSTICK_FIXED;
+}
+
+void _Movetype_CheckStuck(entity this)  // SV_CheckStuck
+{
+       int unstick = _Movetype_UnstickEntity(this); // sets test position entity
+       switch(unstick)
+       {
+               case UNSTICK_FINE:
+                       this.oldorigin = this.origin;
+                       break;
+               case UNSTICK_FIXED:
+                       break; // already sorted
+               case UNSTICK_STUCK:
+                       vector offset = this.oldorigin - this.origin;
+                       if(!_Movetype_TestEntityPosition(offset))
+                               _Movetype_LinkEdict(this, false);
+                       // couldn't unstick, should we warn about this?
+                       break;
+       }
 }
 
 vector _Movetype_ClipVelocity(vector vel, vector norm, float f)  // SV_ClipVelocity
index 85912ee1c33f915ebdf59f907c616ef2f571c4f3..62b7964d98db09a2906f7731658c99085d79d43b 100644 (file)
@@ -26,6 +26,8 @@ const int WATERLEVEL_SUBMERGED = 3;
 #define PHYS_JUMPSTEP(s)                    STAT(MOVEVARS_JUMPSTEP)
 #define PHYS_WALLFRICTION(s)                STAT(MOVEVARS_WALLFRICTION)
 
+#define PHYS_WALLCLIP(s)                                       STAT(MOVEVARS_WALLCLIP)
+
 #ifdef CSQC
 .float bouncestop;
 .float bouncefactor;
@@ -55,6 +57,8 @@ const int WATERLEVEL_SUBMERGED = 3;
 
 void set_movetype(entity this, int mt);
 
+.float pm_time;
+
 .float move_movetype;
 .float move_time;
 //.vector move_origin;
@@ -76,10 +80,16 @@ void set_movetype(entity this, int mt);
 .float move_suspendedinair;
 .float move_didgravity;
 
+// unsticking
+const int UNSTICK_FINE = 0;
+const int UNSTICK_FIXED = 1;
+const int UNSTICK_STUCK = 2;
+
 void _Movetype_WallFriction(entity this, vector stepnormal);
 int _Movetype_FlyMove(entity this, float dt, bool applygravity, vector stepnormal, float stepheight);
 void _Movetype_CheckVelocity(entity this);
 void _Movetype_CheckWaterTransition(entity ent);
+void _Movetype_CheckStuck(entity this);
 float _Movetype_CheckWater(entity ent);
 void _Movetype_LinkEdict_TouchAreaGrid(entity this);
 void _Movetype_LinkEdict(entity this, float touch_triggers);
@@ -94,7 +104,7 @@ void Movetype_Physics_NoMatchServer(entity this);
 void _Movetype_LinkEdict(entity this, float touch_triggers);
 void _Movetype_LinkEdict_TouchAreaGrid(entity this);
 
-float _Movetype_UnstickEntity(entity this);
+int _Movetype_UnstickEntity(entity this);
 
 const int MAX_CLIP_PLANES = 5;
 
@@ -122,6 +132,8 @@ const int MOVETYPE_ANGLENOCLIP      = 1;
 const int MOVETYPE_ANGLECLIP        = 2;
 #endif
 
+const int MOVETYPE_QCPLAYER = 150; // QC-driven player physics, no think functions!
+
 const int FL_ONSLICK = BIT(20);
 
 const int MOVETYPE_FAKEPUSH         = 13;
index c20e82e8342825a9282142d288281ceffea2583d..c0f2fac9640be94fa6c550536f029c2197180e38 100644 (file)
@@ -8,7 +8,7 @@ void _Movetype_Physics_Walk(entity this, float dt)  // SV_WalkMove
                return;
 
        if (GAMEPLAYFIX_UNSTICKPLAYERS(this))
-               _Movetype_UnstickEntity(this);
+               _Movetype_CheckStuck(this);
 
        bool applygravity = (!_Movetype_CheckWater(this) && this.move_movetype == MOVETYPE_WALK && !(this.flags & FL_WATERJUMP));
 
@@ -20,6 +20,14 @@ void _Movetype_Physics_Walk(entity this, float dt)  // SV_WalkMove
        vector start_origin = this.origin;
        vector start_velocity = this.velocity;
 
+       if(PHYS_WALLCLIP(this) && this.pm_time)
+       {
+               if(dt >= this.pm_time || (this.flags & FL_WATERJUMP))
+                       this.pm_time = 0;
+               else
+                       this.pm_time -= dt;
+       }
+
        int clip = _Movetype_FlyMove(this, dt, applygravity, stepnormal, GAMEPLAYFIX_STEPMULTIPLETIMES(this) ? PHYS_STEPHEIGHT(this) : 0);
 
        if (GAMEPLAYFIX_DOWNTRACEONGROUND(this) && !(clip & 1))
@@ -45,6 +53,8 @@ void _Movetype_Physics_Walk(entity this, float dt)  // SV_WalkMove
        // if the move did not hit the ground at any point, we're not on ground
        if (!(clip & 1))
                UNSET_ONGROUND(this);
+       else if(PHYS_WALLCLIP(this) && !this.groundentity && (PHYS_WALLCLIP(this) == 2 || start_velocity.z < -200)) // don't do landing time if we were just going down a slope
+               this.pm_time = 0.25;
 
        _Movetype_CheckVelocity(this);
        _Movetype_LinkEdict(this, true);
@@ -144,7 +154,8 @@ void _Movetype_Physics_Walk(entity this, float dt)  // SV_WalkMove
        }
 
        // move down
-       vector downmove = '0 0 1' * (-PHYS_STEPHEIGHT(this) + start_velocity.z * dt);
+       vector downmove = '0 0 0';
+       downmove.z = -PHYS_STEPHEIGHT(this) + start_velocity.z * dt;
        _Movetype_PushEntity(this, downmove, true);
        if(wasfreed(this))
                return;
@@ -159,6 +170,13 @@ void _Movetype_Physics_Walk(entity this, float dt)  // SV_WalkMove
        {
                // this has been disabled so that you can't jump when you are stepping
                // up while already jumping (also known as the Quake2 double jump bug)
+               // LordHavoc: disabled this check so you can walk on monsters/players
+               //if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP)
+               if(GAMEPLAYFIX_STEPDOWN(this) == 2)
+               {
+                       SET_ONGROUND(this);
+                       this.groundentity = trace_ent;
+               }
        }
        else
        {
index 2f4ebb1ff002c9c37278901d70a4838bb0e043e1..87d456c018fb4ad7b5f52243b73d9e13c4f7c569 100644 (file)
 // client side physics
 bool Physics_Valid(string thecvar)
 {
-       return autocvar_g_physics_clientselect && thecvar != "" && thecvar && thecvar != "default" && strhasword(autocvar_g_physics_clientselect_options, thecvar);
+       return thecvar != "" && thecvar && thecvar != "default" && strhasword(autocvar_g_physics_clientselect_options, thecvar);
 }
 
 float Physics_ClientOption(entity this, string option, float defaultval)
 {
+       if(!autocvar_g_physics_clientselect)
+               return defaultval;
+
        if(IS_REAL_CLIENT(this) && Physics_Valid(CS(this).cvar_cl_physics))
        {
                string s = strcat("g_physics_", CS(this).cvar_cl_physics, "_", option);
                if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
                        return cvar(s);
        }
-       if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "")
+       if(autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "" && autocvar_g_physics_clientselect_default != "default")
        {
+               // NOTE: not using Physics_Valid here, so the default can be forced to something normally unavailable
                string s = strcat("g_physics_", autocvar_g_physics_clientselect_default, "_", option);
                if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
                        return cvar(s);
@@ -45,12 +49,13 @@ void Physics_UpdateStats(entity this)
        STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod;
        STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed", autocvar_sv_maxspeed) * maxspd_mod; // also slow walking
 
-       STAT(PL_MIN, this) = autocvar_sv_player_mins;
-       STAT(PL_MAX, this) = autocvar_sv_player_maxs;
-       STAT(PL_VIEW_OFS, this) = autocvar_sv_player_viewoffset;
-       STAT(PL_CROUCH_MIN, this) = autocvar_sv_player_crouch_mins;
-       STAT(PL_CROUCH_MAX, this) = autocvar_sv_player_crouch_maxs;
-       STAT(PL_CROUCH_VIEW_OFS, this) = autocvar_sv_player_crouch_viewoffset;
+       bool vq3compat = autocvar_sv_vq3compat && autocvar_sv_vq3compat_changehitbox; // NOTE: these hitboxes are off by 1 due to engine differences
+       STAT(PL_MIN, this) = (vq3compat) ? '-15 -15 -24' : autocvar_sv_player_mins;
+       STAT(PL_MAX, this) = (vq3compat) ? '15 15 32' : autocvar_sv_player_maxs;
+       STAT(PL_VIEW_OFS, this) = (vq3compat) ? '0 0 26' : autocvar_sv_player_viewoffset;
+       STAT(PL_CROUCH_MIN, this) = (vq3compat) ? '-15 -15 -24' : autocvar_sv_player_crouch_mins;
+       STAT(PL_CROUCH_MAX, this) = (vq3compat) ? '15 15 16' : autocvar_sv_player_crouch_maxs;
+       STAT(PL_CROUCH_VIEW_OFS, this) = (vq3compat) ? '0 0 12' : autocvar_sv_player_crouch_viewoffset;
 
        // old stats
        // fix some new settings
@@ -119,6 +124,8 @@ void PM_ClientMovement_UpdateStatus(entity this)
                }
        }
        bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
+       if(this.viewloc && !(this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && PHYS_CS(this).movement.x < 0)
+               do_crouch = true;
        if (have_hook) {
                do_crouch = false;
        //} else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
@@ -809,7 +816,8 @@ bool IsFlying(entity this)
                return false;
        if(this.waterlevel >= WATERLEVEL_SWIMMING)
                return false;
-       traceline(this.origin, this.origin - '0 0 48', MOVE_NORMAL, this);
+       tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 24', MOVE_NORMAL, this);
+       //traceline(this.origin, this.origin - '0 0 48', MOVE_NORMAL, this);
        if(trace_fraction < 1)
                return false;
        return true;
index da48fa698c2086e34049c7fc1ffeb0f9599d5a87..29c028f03267500aed3f84994ff0b524e14c6c7c 100644 (file)
@@ -15,7 +15,7 @@
 .float swamp_slowdown;
 .float lastflags;
 .float lastground;
-.float wasFlying;
+.bool wasFlying;
 
 .int buttons_old;
 .vector movement_old;
@@ -109,6 +109,7 @@ bool IsFlying(entity a);
 #define PHYS_INPUT_BUTTON_ZOOMSCRIPT(s)     PHYS_INPUT_BUTTON_BUTTON9(s)
 #define PHYS_INPUT_BUTTON_JETPACK(s)        PHYS_INPUT_BUTTON_BUTTON10(s)
 #define PHYS_INPUT_BUTTON_DODGE(s)                     PHYS_INPUT_BUTTON_BUTTON11(s)
+#define PHYS_INPUT_BUTTON_MINIGAME(s)          PHYS_INPUT_BUTTON_BUTTON14(s)
 
 #ifdef CSQC
 STATIC_INIT(PHYS_INPUT_BUTTON)
index cde6a519028093268ef95fc568e91184a40d3368..2c9bbcde805eb9770015c385877971563b872731 100644 (file)
@@ -55,7 +55,7 @@ void PlayerStats_GameReport_AddPlayer(entity e)
        }
 }
 
-void PlayerStats_GameReport_AddTeam(float t)
+void PlayerStats_GameReport_AddTeam(int t)
 {
        if(PS_GR_OUT_DB < 0) { return; }
 
@@ -157,7 +157,7 @@ void PlayerStats_GameReport_FinalizePlayer(entity p)
        strfree(p.playerstats_id);
 }
 
-void PlayerStats_GameReport(float finished)
+void PlayerStats_GameReport(bool finished)
 {
        if(PS_GR_OUT_DB < 0) { return; }
 
@@ -258,6 +258,7 @@ void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that
 }
 
 // this... is a hack, a temporary one until we get a proper duel gametype
+// TODO: remove duel hack after servers have migrated to the proper duel gametype!
 string PlayerStats_GetGametype()
 {
        if(IS_GAMETYPE(DEATHMATCH) && autocvar_g_maxplayers == 2)
index 28f985e27d24a214afc7519a956f70dd79526cef..d27dd0ed1f775038bb904e57327876a84d45314d 100644 (file)
@@ -54,7 +54,7 @@ const string PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD = "achievement-firstblood";
 const string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM = "achievement-firstvictim";
 
 // delay map switch until this is set
-float PlayerStats_GameReport_DelayMapVote;
+bool PlayerStats_GameReport_DelayMapVote;
 
 // call at initialization
 void PlayerStats_GameReport_Init();
index 328486d35470cfc3594e437742d6428d2426a14c..fcd4d9a459983fc06a2e35fdb1cd5e8db753e8b9 100644 (file)
@@ -8,6 +8,7 @@ bool autocvar_bot_sound_monopoly;
 .entity realowner;
 bool sound_allowed(int to, entity e)
 {
+       if(!e) return true; // save on a few checks
        for ( ; ; )
        {
                if (e.classname == "body") e = e.enemy;
index 37813ef716c0bd5f393bc45a1f2fad39036e7b0b..2e3a262a59e9662c2456e77a7277f2cb9bd9acef 100644 (file)
@@ -7,7 +7,7 @@ void PlayerState_attach(entity this)
 {
        this._ps = NEW(PlayerState, this);
 
-       Inventory_new(this);
+       Inventory_new(PS(this));
 }
 
 void PlayerState_detach(entity this)
@@ -18,11 +18,10 @@ void PlayerState_detach(entity this)
 
        if (ps.m_client != this) return;  // don't own state, spectator
        ps.ps_push(ps, this);
+    Inventory_delete(ps);
 
        FOREACH_CLIENT(PS(it) == ps, { PS(it) = NULL; });
        delete(ps);
-
-    Inventory_delete(this);
 }
 
 void GetCvars(entity this, entity store, int);
@@ -52,8 +51,6 @@ void ClientState_attach(entity this)
        entcs_attach(this);
        anticheat_init(this);
        W_HitPlotOpen(this);
-
-       bot_clientconnect(this);
 }
 
 void bot_clientdisconnect(entity this);
index 9b8f04041780fd55f707331540c74ab99b0e8e88..cf51ea66b3ebf7b24d86c16c89756347fa5bca0c 100644 (file)
@@ -301,6 +301,11 @@ bool autocvar_sv_slick_applygravity;
 #endif
 REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
 
+#ifdef SVQC
+bool autocvar_sv_vq3compat;
+#endif
+REGISTER_STAT(VQ3COMPAT, bool, autocvar_sv_vq3compat)
+
 #ifdef SVQC
 #include "physics/movetypes/movetypes.qh"
 float warmup_limit;
@@ -360,6 +365,10 @@ REGISTER_STAT(MOVEVARS_STEPHEIGHT, float, autocvar_sv_stepheight)
 REGISTER_STAT(MOVEVARS_AIRACCEL_QW, float)
 REGISTER_STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, float)
 REGISTER_STAT(MOVEVARS_SPECIALCOMMAND, bool)
+#ifdef SVQC
+int autocvar_sv_wallclip;
+#endif
+REGISTER_STAT(MOVEVARS_WALLCLIP, int, autocvar_sv_wallclip)
 
 
 #ifdef CSQC
index 140b619c5ec09cae3f8c62fcf1e87bb6c4b61c2b..dfee5330df713431f4afba5cc9be713504171323 100644 (file)
@@ -402,7 +402,7 @@ bool have_pickup_item(entity this)
        return true;
 }
 
-void Item_Show (entity e, float mode)
+void Item_Show (entity e, int mode)
 {
        e.effects &= ~(EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST);
        e.ItemStatus &= ~ITS_STAYWEP;
@@ -613,14 +613,18 @@ float adjust_respawntime(float normal_respawntime) {
                return normal_respawntime;
        }
 
-       CheckAllowedTeams(NULL);
-       GetTeamCounts(NULL);
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       TeamBalance_GetTeamCounts(balance, NULL);
        int players = 0;
-       if (c1 != -1) players += c1;
-       if (c2 != -1) players += c2;
-       if (c3 != -1) players += c3;
-       if (c4 != -1) players += c4;
-
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if (TeamBalance_IsTeamAllowed(balance, i))
+               {
+                       players += TeamBalance_GetNumberOfPlayers(balance, i);
+               }
+       }
+       TeamBalance_Destroy(balance);
+       
        if (players >= 2) {
                return normal_respawntime * (r / (players + o) + l);
        } else {
@@ -852,7 +856,7 @@ float Item_GiveTo(entity item, entity player)
                return 0;
 
        // crude hack to enforce switching weapons
-       if(g_cts && item.itemdef.instanceOfWeaponPickup)
+       if(g_cts && item.itemdef.instanceOfWeaponPickup && !CS(player).cvar_cl_cts_noautoswitch)
        {
                for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
@@ -1313,7 +1317,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                        || (def.instanceOfHealth && def != ITEM_HealthSmall)
                        || (def.instanceOfArmor && def != ITEM_ArmorSmall)
                        || (itemid & (IT_KEY1 | IT_KEY2))
-               ) this.target = "###item###"; // for finding the nearest item using find()
+               ) this.target = "###item###"; // for finding the nearest item using findnearest
 
                Item_ItemsTime_SetTime(this, 0);
        }
index 9fdb0b0925798580c57106a7eb598c24e3a5ad41..4d3f45380069e9cd6fe397265e2c98291d2132ad 100644 (file)
@@ -62,7 +62,7 @@ const float ITEM_RESPAWN_TICKS = 10;
 
 .float item_respawncounter;
 
-void Item_Show (entity e, float mode);
+void Item_Show (entity e, int mode);
 
 void Item_Respawn (entity this);
 
@@ -107,7 +107,7 @@ float weapon_pickupevalfunc(entity player, entity item);
 float ammo_pickupevalfunc(entity player, entity item);
 float healtharmor_pickupevalfunc(entity player, entity item);
 
-.float is_item;
+.bool is_item;
 .entity itemdef;
 void _StartItem(entity this, entity def, float defaultrespawntime, float defaultrespawntimejitter);
 
index 57d644c0448549e13bff834f7cb6fd289d983cc3..62bb2db7cdda28a8e6e63da5cf6024e59f570a3e 100644 (file)
@@ -1,5 +1,7 @@
 #pragma once
 
+const int NUM_TEAMS = 4; ///< Number of teams in the game.
+
 #ifdef TEAMNUMBERS_THAT_ARENT_STUPID
 const int NUM_TEAM_1 = 1;  // red
 const int NUM_TEAM_2 = 2; // blue
@@ -54,11 +56,11 @@ const string STATIC_NAME_TEAM_3 = "Yellow";
 const string STATIC_NAME_TEAM_4 = "Pink";
 
 #ifdef CSQC
-float teamplay;
-float myteam;
+bool teamplay;
+int myteam;
 #endif
 
-string Team_ColorCode(float teamid)
+string Team_ColorCode(int teamid)
 {
        switch(teamid)
        {
@@ -71,7 +73,7 @@ string Team_ColorCode(float teamid)
        return "^7";
 }
 
-vector Team_ColorRGB(float teamid)
+vector Team_ColorRGB(int teamid)
 {
        switch(teamid)
        {
@@ -84,7 +86,7 @@ vector Team_ColorRGB(float teamid)
     return '0 0 0';
 }
 
-string Team_ColorName(float teamid)
+string Team_ColorName(int teamid)
 {
        switch(teamid)
        {
@@ -98,7 +100,7 @@ string Team_ColorName(float teamid)
 }
 
 // used for replacement in filenames or such where the name CANNOT be allowed to be translated
-string Static_Team_ColorName(float teamid)
+string Static_Team_ColorName(int teamid)
 {
        switch(teamid)
        {
@@ -125,12 +127,12 @@ float Team_ColorToTeam(string team_color)
        return -1;
 }
 
-/// \brief Returns whether team is valid.
-/// \param[in] team_ Team to check.
+/// \brief Returns whether team value is valid.
+/// \param[in] team_num Team to check.
 /// \return True if team is valid, false otherwise.
-bool Team_IsValidTeam(int team_)
+bool Team_IsValidTeam(int team_num)
 {
-       switch (team_)
+       switch (team_num)
        {
                case NUM_TEAM_1:
                case NUM_TEAM_2:
@@ -143,12 +145,12 @@ bool Team_IsValidTeam(int team_)
        return false;
 }
 
-/// \brief Returns whether team number is valid.
-/// \param[in] number Team number to check.
-/// \return True if team number is valid, false otherwise.
-bool Team_IsValidNumber(int number)
+/// \brief Returns whether the team index is valid.
+/// \param[in] index Team index to check.
+/// \return True if team index is valid, false otherwise.
+bool Team_IsValidIndex(int index)
 {
-       switch (number)
+       switch (index)
        {
                case 1:
                case 2:
@@ -161,36 +163,60 @@ bool Team_IsValidNumber(int number)
        return false;
 }
 
-float Team_NumberToTeam(float number)
+/// \brief Converts team index into team value.
+/// \param[in] index Team index to convert.
+/// \return Team value.
+int Team_IndexToTeam(int index)
 {
-       switch(number)
+       switch (index)
        {
                case 1: return NUM_TEAM_1;
                case 2: return NUM_TEAM_2;
                case 3: return NUM_TEAM_3;
                case 4: return NUM_TEAM_4;
        }
-
        return -1;
 }
 
-float Team_TeamToNumber(float teamid)
+/// \brief Converts team value into team index.
+/// \param[in] team_num Team value to convert.
+/// \return Team index.
+int Team_TeamToIndex(int team_num)
 {
-       switch(teamid)
+       switch (team_num)
        {
                case NUM_TEAM_1: return 1;
                case NUM_TEAM_2: return 2;
                case NUM_TEAM_3: return 3;
                case NUM_TEAM_4: return 4;
        }
-
        return -1;
 }
 
+/// \brief Converts team value into bit value that is used in team bitmasks.
+/// \param[in] team_num Team value to convert.
+/// \return Team bit.
+int Team_TeamToBit(int team_num)
+{
+       if (!Team_IsValidTeam(team_num))
+       {
+               return 0;
+       }
+       return BIT(Team_TeamToIndex(team_num) - 1);
+}
+
+/// \brief Converts team index into bit value that is used in team bitmasks.
+/// \param[in] index Team index to convert.
+/// \return Team bit.
+int Team_IndexToBit(int index)
+{
+       return BIT(index - 1);
+}
+
 
 // legacy aliases for shitty code
-#define TeamByColor(teamid) (Team_TeamToNumber(teamid) - 1)
-#define ColorByTeam(number) Team_NumberToTeam(number + 1)
+#define TeamByColor(teamid) (Team_TeamToIndex(teamid) - 1)
+#define ColorByTeam(number) Team_IndexToTeam(number + 1)
 
 // useful aliases
 #define Team_ColorName_Lower(teamid) strtolower(Team_ColorName(teamid))
@@ -203,8 +229,8 @@ float Team_TeamToNumber(float teamid)
 #define Team_FullName(teamid) strcat(Team_ColorName(teamid), " ", NAME_TEAM, "^7")
 #define Team_ColoredFullName(teamid) strcat(Team_ColorCode(teamid), Team_ColorName(teamid), " ", NAME_TEAM, "^7")
 
-#define Team_NumberToFullName(number) Team_FullName(Team_NumberToTeam(number))
-#define Team_NumberToColoredFullName(number) Team_ColoredFullName(Team_NumberToTeam(number))
+#define Team_IndexToFullName(index) Team_FullName(Team_IndexToTeam(index))
+#define Team_IndexToColoredFullName(index) Team_ColoredFullName(Team_IndexToTeam(index))
 
 // replace these flags in a string with the strings provided
 #define TCR(input,type,team) strreplace("^TC", COL_TEAM_##team, strreplace("^TT", strtoupper(type##_TEAM_##team), input))
index ac68003a6cde83c1a6b652959af1f349240654e1..6cdda5103955a428443e1194c0711c310d692fe5 100644 (file)
@@ -176,7 +176,10 @@ void turret_draw2d(entity this)
        }
 
        o = drawspritearrow(o, M_PI, rgb, a, SPRITE_ARROW_SCALE * t);
-       o = drawsprite_TextOrIcon(true, o, M_PI, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
+       if(autocvar_g_waypointsprite_turrets_text)
+       {
+               o = drawsprite_TextOrIcon(true, o, M_PI, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
+       }
        drawhealthbar(
                        o,
                        0,
index 95ee419815a14a0741644b8a84ddcf6bd5ea3b97..37fb5f0d524d78bc7b8180d049213ab407fd72da 100644 (file)
@@ -688,6 +688,7 @@ void turret_track(entity this)
  + TFL_TARGETSELECT_LOS
  + TFL_TARGETSELECT_PLAYERS
  + TFL_TARGETSELECT_MISSILES
+ + TFL_TARGETSELECT_VEHICLES
  - TFL_TARGETSELECT_TRIGGERTARGET
  + TFL_TARGETSELECT_ANGLELIMITS
  + TFL_TARGETSELECT_RANGELIMITS
@@ -716,7 +717,7 @@ float turret_validate_target(entity e_turret, entity e_target, float validate_fl
        if(!checkpvs(e_target.origin, e_turret))
                return -1;
 
-       if(e_target.alpha <= 0.3)
+       if(e_target.alpha != 0 && e_target.alpha <= 0.3)
                return -1;
 
        if(MUTATOR_CALLHOOK(TurretValidateTarget, e_turret, e_target, validate_flags))
@@ -730,15 +731,17 @@ float turret_validate_target(entity e_turret, entity e_target, float validate_fl
                return -5;
 
        // Cant touch this
+       if (GetResourceAmount(e_target, RESOURCE_HEALTH) <= 0)
+               return -6;
+       else if (STAT(FROZEN, e_target))
+               return -6;
+
+       // vehicle
        if(IS_VEHICLE(e_target))
        {
-               if (e_target.vehicle_health <= 0)
-                       return -6;
+               if ((validate_flags & TFL_TARGETSELECT_VEHICLES) && !e_target.owner)
+                       return -7;
        }
-       else if (GetResourceAmount(e_target, RESOURCE_HEALTH) <= 0)
-               return -6;
-       else if(STAT(FROZEN, e_target) > 0)
-               return -6;
 
        // player
        if (IS_CLIENT(e_target))
index 7f9b746cd379e88b8a034ddc3e95199697b236c4..aa5a50c9e67dd6d8b8e55953d55b7cde0372b3a1 100644 (file)
@@ -62,19 +62,20 @@ ENDCLASS(Turret)
 // target selection flags
 .int target_select_flags;
 .int target_validate_flags;
-const int TFL_TARGETSELECT_NO = 2; // don't automatically find targets
-const int TFL_TARGETSELECT_LOS = 4; // require line of sight to find targets
-const int TFL_TARGETSELECT_PLAYERS = 8; // target players
-const int TFL_TARGETSELECT_MISSILES = 16; // target projectiles
-const int TFL_TARGETSELECT_TRIGGERTARGET = 32; // respond to turret_trigger_target events
-const int TFL_TARGETSELECT_ANGLELIMITS = 64; // apply extra angular limits to target selection
-const int TFL_TARGETSELECT_RANGELIMITS = 128; // limit target selection range
-const int TFL_TARGETSELECT_TEAMCHECK = 256; // don't attack teammates
-const int TFL_TARGETSELECT_NOBUILTIN = 512; // only attack targets when triggered
-const int TFL_TARGETSELECT_OWNTEAM = 1024; // only attack teammates
-const int TFL_TARGETSELECT_NOTURRETS = 2048; // don't attack other turrets
-const int TFL_TARGETSELECT_FOV = 4096; // extra limits to attack range
-const int TFL_TARGETSELECT_MISSILESONLY = 8192; // only attack missiles
+const int TFL_TARGETSELECT_NO = BIT(1); // don't automatically find targets
+const int TFL_TARGETSELECT_LOS = BIT(2); // require line of sight to find targets
+const int TFL_TARGETSELECT_PLAYERS = BIT(3); // target players
+const int TFL_TARGETSELECT_MISSILES = BIT(4); // target projectiles
+const int TFL_TARGETSELECT_TRIGGERTARGET = BIT(5); // respond to turret_trigger_target events
+const int TFL_TARGETSELECT_ANGLELIMITS = BIT(6); // apply extra angular limits to target selection
+const int TFL_TARGETSELECT_RANGELIMITS = BIT(7); // limit target selection range
+const int TFL_TARGETSELECT_TEAMCHECK = BIT(8); // don't attack teammates
+const int TFL_TARGETSELECT_NOBUILTIN = BIT(9); // only attack targets when triggered
+const int TFL_TARGETSELECT_OWNTEAM = BIT(10); // only attack teammates
+const int TFL_TARGETSELECT_NOTURRETS = BIT(11); // don't attack other turrets
+const int TFL_TARGETSELECT_FOV = BIT(12); // extra limits to attack range
+const int TFL_TARGETSELECT_MISSILESONLY = BIT(13); // only attack missiles
+const int TFL_TARGETSELECT_VEHICLES = BIT(14); // target manned vehicles
 
 // aim flags
 .int aim_flags;
index 811e386f5b4e38bc85cb90f8f0ade95e0e3b3e8c..ba2e89c2c12e1d8024eeaacdd57f573e1c806c8b 100644 (file)
@@ -22,10 +22,10 @@ METHOD(HunterKiller, tr_setup, void(HunterKiller this, entity it))
 {
     it.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE;
     it.aim_flags = TFL_AIM_SIMPLE;
-    it.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TRIGGERTARGET | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK;
+    it.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_VEHICLES | TFL_TARGETSELECT_TRIGGERTARGET | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK;
     it.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_TEAMCHECK  | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AFF;
     it.shoot_flags = TFL_SHOOT_CLEARTARGET;
-    it.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TEAMCHECK;
+    it.target_validate_flags = TFL_TARGETSELECT_VEHICLES | TFL_TARGETSELECT_TEAMCHECK;
 
     it.turret_addtarget = turret_hk_addtarget;
 }
index 619d7a9072d9a1e127e8e4698caa0c99bd8fc6f2..a56e7de3833a6fe4d997cda5fa4081de332c95c8 100644 (file)
@@ -17,7 +17,7 @@ METHOD(MachineGunTurretAttack, wr_think, void(entity thiswep, entity actor, .ent
             actor.tur_head = actor;
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, 0, w_ready);
         }
-        fireBullet (actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_MACHINEGUN.m_id, 0);
+        fireBullet(actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_MACHINEGUN.m_id, EFFECT_BULLET);
         W_MachineGun_MuzzleFlash(actor, weaponentity);
         setattachment(actor.(weaponentity).muzzle_flash, actor.tur_head, "tag_fire");
     }
index d81b738ed4510b8565584752f76c50c785765471..bbe59aeac90aeca4980be687fb8257eb60ae545e 100644 (file)
@@ -16,7 +16,7 @@ METHOD(WalkerTurretAttack, wr_think, void(entity thiswep, entity actor, .entity
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready);
         }
         sound (actor, CH_WEAPON_A, SND_UZI_FIRE, VOL_BASE, ATTEN_NORM);
-        fireBullet (actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_WALK_GUN.m_id, 0);
+        fireBullet(actor, weaponentity, actor.tur_shotorg, actor.tur_shotdir_updated, actor.shot_spread, 0, actor.shot_dmg, actor.shot_force, DEATH_TURRET_WALK_GUN.m_id, EFFECT_BULLET);
         Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, actor.tur_shotorg, actor.tur_shotdir_updated * 1000, 1);
     }
 }
index 183302b3a1e0460c246c9c1c8357edfbace43c53..a7e9c42104d222e04d7b6d1d5884c6376eb7b060 100644 (file)
@@ -1298,6 +1298,7 @@ float matchacl(string acl, string str)
                if(s == t)
                {
                        r = d;
+                       break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
                }
        }
        return r;
index 716dfe8d0c130225041502345a182873aa7c111f..be8d468fa78dca4048312bc69df8fc4c52fe6c6f 100644 (file)
@@ -385,7 +385,7 @@ bool vehicle_addplayerslot( entity _owner,
 
 vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname,
                                                 float _pichlimit_min, float _pichlimit_max,
-                                                float _rotlimit_min, float _rotlimit_max, float _aimspeed)
+                                                float _rotlimit_min, float _rotlimit_max, float _aimspeed, float dt)
 {
        vector vtmp, vtag;
        float ftmp;
@@ -393,7 +393,7 @@ vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string
        vtmp = vectoangles(normalize(_target - vtag));
        vtmp = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(_vehic.angles), AnglesTransform_FromAngles(vtmp))) - _turrret.angles;
        vtmp = AnglesTransform_Normalize(vtmp, true);
-       ftmp = _aimspeed * frametime;
+       ftmp = _aimspeed * dt;
        vtmp_y = bound(-ftmp, vtmp_y, ftmp);
        vtmp_x = bound(-ftmp, vtmp_x, ftmp);
        _turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max);
@@ -587,7 +587,7 @@ void vehicles_regen(entity this, float timer, .float regen_field, float field_ma
        if(timer + rpause < time)
        {
                if(_healthscale)
-                       regen = regen * (this.vehicle_health / this.max_health);
+                       regen = regen * (GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health);
 
                this.(regen_field) = min(this.(regen_field) + regen * delta_time, field_max);
 
@@ -596,6 +596,23 @@ void vehicles_regen(entity this, float timer, .float regen_field, float field_ma
        }
 }
 
+void vehicles_regen_resource(entity this, float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale, int resource)
+{
+       float resource_amount = GetResourceAmount(this, resource);
+
+       if(resource_amount < field_max)
+       if(timer + rpause < time)
+       {
+               if(_healthscale)
+                       regen = regen * (resource_amount / this.max_health);
+
+               SetResourceAmount(this, resource, min(resource_amount + regen * delta_time, field_max));
+
+               if(this.owner)
+                       this.owner.(regen_field) = (GetResourceAmount(this, resource) / field_max) * 100;
+       }
+}
+
 void shieldhit_think(entity this)
 {
        this.alpha -= 0.1;
@@ -613,7 +630,7 @@ void shieldhit_think(entity this)
 
 void vehicles_painframe(entity this)
 {
-       int myhealth = ((this.owner) ? this.owner.vehicle_health : ((this.vehicle_health / this.max_health) * 100));
+       int myhealth = ((this.owner) ? this.owner.vehicle_health : ((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 100));
 
        if(myhealth <= 50)
        if(this.pain_frame < time)
@@ -684,7 +701,7 @@ void vehicles_damage(entity this, entity inflictor, entity attacker, float damag
 
                if(this.vehicle_shield < 0)
                {
-                       this.vehicle_health -= fabs(this.vehicle_shield);
+                       TakeResource(this, RESOURCE_HEALTH, fabs(this.vehicle_shield));
                        this.vehicle_shieldent.colormod = '2 0 0';
                        this.vehicle_shield = 0;
                        this.vehicle_shieldent.alpha = 0.75;
@@ -699,7 +716,7 @@ void vehicles_damage(entity this, entity inflictor, entity attacker, float damag
        }
        else
        {
-               this.vehicle_health -= damage;
+               TakeResource(this, RESOURCE_HEALTH, damage);
 
                if(sound_allowed(MSG_BROADCAST, attacker))
                        spamsound (this, CH_PAIN, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
@@ -710,7 +727,7 @@ void vehicles_damage(entity this, entity inflictor, entity attacker, float damag
        else
                this.velocity += force;
 
-       if(this.vehicle_health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                if(this.owner)
                        if(this.vehicle_flags & VHF_DEATHEJECT)
@@ -730,14 +747,12 @@ void vehicles_damage(entity this, entity inflictor, entity attacker, float damag
 bool vehicles_heal(entity targ, entity inflictor, float amount, float limit)
 {
        float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
-       //if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
-       if(targ.vehicle_health <= 0 || targ.vehicle_health >= true_limit)
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
                return false;
 
-       targ.vehicle_health = min(targ.vehicle_health + amount, true_limit);
-       //GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
-       //if(targ.owner)
-               //targ.owner.vehicle_health = (targ.vehicle_health / targ.max_health) * 100;
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       if(targ.owner)
+               targ.owner.vehicle_health = (GetResourceAmount(targ, RESOURCE_HEALTH) / targ.max_health) * 100;
        return true;
 }
 
@@ -951,7 +966,7 @@ bool vehicle_impulse(entity this, int imp)
 
 void vehicles_enter(entity pl, entity veh)
 {
-   // Remove this when bots know how to use vehicles
+       // Remove this when bots know how to use vehicles
        if((IS_BOT_CLIENT(pl) && !autocvar_g_vehicles_allow_bots))
                return;
 
index 0cc9da56ea11a57779850fc85f1bdd53ca0864e1..a1f23c1dfcc1b95522a3300296737dc4a927b026 100644 (file)
@@ -45,7 +45,7 @@ float autocvar_g_vehicles_weapon_damagerate = 2;
 .entity gunner1;
 .entity gunner2;
 
-.float vehicle_health = _STAT(VEHICLESTAT_HEALTH);  /// If ent is player this is 0..100 indicating precentage of health left on vehicle. If ent is vehicle, this is the real health value.
+.float vehicle_health = _STAT(VEHICLESTAT_HEALTH);  /// If ent is player this is 0..100 indicating precentage of health left on vehicle. Vehicle's value is the health resource
 .float vehicle_energy = _STAT(VEHICLESTAT_ENERGY);  /// If ent is player this is 0..100 indicating precentage of energy left on vehicle. If ent is vehicle, this is the real energy value.
 .float vehicle_shield = _STAT(VEHICLESTAT_SHIELD);  /// If ent is player this is 0..100 indicating precentage of shield left on vehicle. If ent is vehicle, this is the real shield value.
 
@@ -99,6 +99,9 @@ float vehicles_exit_running;
 #define VEHICLE_UPDATE_PLAYER(ply,vehi,fld,vhname) \
        ply.vehicle_##fld = (vehi.vehicle_##fld / autocvar_g_vehicle_##vhname##_##fld) * 100
 
+#define VEHICLE_UPDATE_PLAYER_RESOURCE(ply,vehi,fld,vhname,res) \
+       ply.vehicle_##fld = (GetResourceAmount(vehi, res) / autocvar_g_vehicle_##vhname##_##fld) * 100
+
 .float vehicle_enter_delay; // prevent players jumping to and from vehicles instantly
 
 void vehicles_exit(entity vehic, int eject);
index c340d947035617492da6d224bbede286baf8e7be..84f5e144bfa6cb257ef272a97822c99fd8eb2aa5 100644 (file)
@@ -33,9 +33,9 @@ float autocvar_g_vehicle_bumblebee_cannon_ammo = 100;
 float autocvar_g_vehicle_bumblebee_cannon_ammo_regen = 100;
 float autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause = 1;
 
-float autocvar_g_vehicle_bumblebee_cannon_lock = 0;
+float autocvar_g_vehicle_bumblebee_cannon_lock = 1;
 
-float autocvar_g_vehicle_bumblebee_cannon_turnspeed = 160;
+float autocvar_g_vehicle_bumblebee_cannon_turnspeed = 260;
 float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down = 60;
 float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up = 60;
 float autocvar_g_vehicle_bumblebee_cannon_turnlimit_in = 20;
@@ -105,20 +105,28 @@ bool bumblebee_gunner_frame(entity this, float dt)
 
        if(autocvar_g_vehicle_bumblebee_cannon_lock)
        {
-               if(gun.lock_time < time)
+               if(gun.lock_time < time || IS_DEAD(gun.enemy) || STAT(FROZEN, gun.enemy))
                        gun.enemy = NULL;
 
                if(trace_ent)
-                       if(trace_ent.move_movetype)
-                               if(trace_ent.takedamage)
-                                       if(!IS_DEAD(trace_ent) && !STAT(FROZEN, trace_ent))
-                                       {
-                                               if(DIFF_TEAM(trace_ent, this))
-                                               {
-                                                       gun.enemy = trace_ent;
-                                                       gun.lock_time = time + 5;
-                                               }
-                                       }
+               if(trace_ent.move_movetype)
+               if(trace_ent.takedamage)
+               if(!IS_DEAD(trace_ent) && !STAT(FROZEN, trace_ent))
+               {
+                       if(teamplay)
+                       {
+                               if(DIFF_TEAM(trace_ent, this))
+                               {
+                                       gun.enemy = trace_ent;
+                                       gun.lock_time = time + 2.5;
+                               }
+                       }
+                       else
+                       {
+                               gun.enemy = trace_ent;
+                               gun.lock_time = time + 0.5;
+                       }
+               }
        }
 
        if(gun.enemy)
@@ -141,13 +149,13 @@ bool bumblebee_gunner_frame(entity this, float dt)
                UpdateAuxiliaryXhair(this, ad, '1 0 1', 1);
                vehicle_aimturret(vehic, trace_endpos, gun, "fire",
                                                  autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up,
-                                                 _out * -1,  _in,  autocvar_g_vehicle_bumblebee_cannon_turnspeed);
+                                                 _out * -1,  _in,  autocvar_g_vehicle_bumblebee_cannon_turnspeed, dt);
 
        }
        else
                vehicle_aimturret(vehic, _ct, gun, "fire",
                                                  autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up,
-                                                 _out * -1,  _in,  autocvar_g_vehicle_bumblebee_cannon_turnspeed);
+                                                 _out * -1,  _in,  autocvar_g_vehicle_bumblebee_cannon_turnspeed, dt);
 
        if(!forbidWeaponUse(this))
        if(PHYS_INPUT_BUTTON_ATCK(this))
@@ -160,7 +168,7 @@ bool bumblebee_gunner_frame(entity this, float dt)
                                gun.attack_finished_single[0] = time + autocvar_g_vehicle_bumblebee_cannon_refire;
                        }
 
-       VEHICLE_UPDATE_PLAYER(this, vehic, health, bumblebee);
+       VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, bumblebee, RESOURCE_HEALTH);
 
        if(vehic.vehicle_flags & VHF_HASSHIELD)
                VEHICLE_UPDATE_PLAYER(this, vehic, shield, bumblebee);
@@ -389,7 +397,7 @@ void bumblebee_regen(entity this, float dt)
                vehicles_regen(this, this.dmg_time, vehicle_shield, autocvar_g_vehicle_bumblebee_shield, autocvar_g_vehicle_bumblebee_shield_regen_pause, autocvar_g_vehicle_bumblebee_shield_regen, dt, true);
 
        if(this.vehicle_flags  & VHF_HEALTHREGEN)
-               vehicles_regen(this, this.dmg_time, vehicle_health, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_health_regen_pause, autocvar_g_vehicle_bumblebee_health_regen, dt, false);
+               vehicles_regen_resource(this, this.dmg_time, vehicle_health, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_health_regen_pause, autocvar_g_vehicle_bumblebee_health_regen, dt, false, RESOURCE_HEALTH);
 
        if(this.vehicle_flags  & VHF_ENERGYREGEN)
                vehicles_regen(this, this.wait, vehicle_energy, autocvar_g_vehicle_bumblebee_energy, autocvar_g_vehicle_bumblebee_energy_regen_pause, autocvar_g_vehicle_bumblebee_energy_regen, dt, false);
@@ -521,7 +529,7 @@ bool bumblebee_pilot_frame(entity this, float dt)
 
        vang = vehicle_aimturret(vehic, trace_endpos, vehic.gun3, "fire",
                                          autocvar_g_vehicle_bumblebee_raygun_pitchlimit_down * -1,  autocvar_g_vehicle_bumblebee_raygun_pitchlimit_up,
-                                         autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides * -1,  autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides,  autocvar_g_vehicle_bumblebee_raygun_turnspeed);
+                                         autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides * -1,  autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides,  autocvar_g_vehicle_bumblebee_raygun_turnspeed, dt);
 
        if(!forbidWeaponUse(this))
        if((PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)) && (vehic.vehicle_energy > autocvar_g_vehicle_bumblebee_raygun_dps * PHYS_INPUT_FRAMETIME || autocvar_g_vehicle_bumblebee_raygun == 0))
@@ -555,7 +563,7 @@ bool bumblebee_pilot_frame(entity this, float dt)
 
                                                if(IS_VEHICLE(trace_ent))
                                                {
-                                                       if(autocvar_g_vehicle_bumblebee_healgun_sps && trace_ent.vehicle_health <= trace_ent.max_health)
+                                                       if(autocvar_g_vehicle_bumblebee_healgun_sps && GetResourceAmount(trace_ent, RESOURCE_HEALTH) <= trace_ent.max_health)
                                                                trace_ent.vehicle_shield = min(trace_ent.vehicle_shield + autocvar_g_vehicle_bumblebee_healgun_sps * dt, trace_ent.tur_head.max_health);
                                                }
                                                else if(IS_CLIENT(trace_ent))
@@ -584,7 +592,7 @@ bool bumblebee_pilot_frame(entity this, float dt)
        }
        */
 
-       VEHICLE_UPDATE_PLAYER(this, vehic, health, bumblebee);
+       VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, bumblebee, RESOURCE_HEALTH);
        VEHICLE_UPDATE_PLAYER(this, vehic, energy, bumblebee);
 
        this.vehicle_ammo1 = (vehic.gun1.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
@@ -879,7 +887,7 @@ METHOD(Bumblebee, vr_spawn, void(Bumblebee thisveh, entity instance))
     if(!autocvar_g_vehicle_bumblebee_swim)
        instance.dphitcontentsmask |= DPCONTENTS_LIQUIDSMASK;
 
-    instance.vehicle_health = autocvar_g_vehicle_bumblebee_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_bumblebee_health);
     instance.vehicle_shield = autocvar_g_vehicle_bumblebee_shield;
     instance.solid = SOLID_BBOX;
     set_movetype(instance, MOVETYPE_TOSS);
@@ -906,8 +914,8 @@ METHOD(Bumblebee, vr_setup, void(Bumblebee thisveh, entity instance))
 
     instance.vehicle_exit = bumblebee_exit;
     instance.respawntime = autocvar_g_vehicle_bumblebee_respawntime;
-    instance.vehicle_health = autocvar_g_vehicle_bumblebee_health;
-    instance.max_health = instance.vehicle_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_bumblebee_health);
+    instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
     instance.vehicle_shield = autocvar_g_vehicle_bumblebee_shield;
 }
 
@@ -930,7 +938,7 @@ METHOD(Bumblebee, vr_hud, void(Bumblebee thisveh))
     float hudAlpha = autocvar_hud_panel_fg_alpha;
     float blinkValue = 0.55 + sin(time * 7) * 0.45;
     vector tmpPos  = '0 0 0';
-    vector tmpSize = '1 1 1' * hud_fontsize;
+    vector tmpSize = hud_fontsize;
     tmpPos.x = vehicleHud_Pos.x + vehicleHud_Size.x * (520/768);
 
     if(!AuxiliaryXhair[1].draw2d)
index e9c5bf41d002167c5f3b74b17d99de8fad0ad60a..f6909fb42a2100527a5ec668d34fb8010b1d1a87 100644 (file)
@@ -8,7 +8,7 @@ float autocvar_g_vehicle_bumblebee_cannon_damage = 60;
 float autocvar_g_vehicle_bumblebee_cannon_radius = 225;
 float autocvar_g_vehicle_bumblebee_cannon_refire = 0.2;
 float autocvar_g_vehicle_bumblebee_cannon_speed = 20000;
-float autocvar_g_vehicle_bumblebee_cannon_spread = 0.02;
+float autocvar_g_vehicle_bumblebee_cannon_spread = 0;
 float autocvar_g_vehicle_bumblebee_cannon_force = -35;
 #endif
 
index 18e13bcbbe68d3f23bc558dad7b0e0f9427c4d2b..c7f7af8ac6efa82854bca37985be91fb85afabe2 100644 (file)
@@ -359,12 +359,12 @@ bool racer_frame(entity this, float dt)
                vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_racer_shield, autocvar_g_vehicle_racer_shield_regen_pause, autocvar_g_vehicle_racer_shield_regen, dt, true);
 
        if(vehic.vehicle_flags & VHF_HEALTHREGEN)
-               vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, dt, false);
+               vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, dt, false, RESOURCE_HEALTH);
 
        if(vehic.vehicle_flags & VHF_ENERGYREGEN)
                vehicles_regen(vehic, vehic.wait, vehicle_energy, autocvar_g_vehicle_racer_energy, autocvar_g_vehicle_racer_energy_regen_pause, autocvar_g_vehicle_racer_energy_regen, dt, false);
 
-       VEHICLE_UPDATE_PLAYER(player, vehic, health, racer);
+       VEHICLE_UPDATE_PLAYER_RESOURCE(player, vehic, health, racer, RESOURCE_HEALTH);
        VEHICLE_UPDATE_PLAYER(player, vehic, energy, racer);
 
        if(vehic.vehicle_flags & VHF_HASSHIELD)
@@ -514,7 +514,7 @@ METHOD(Racer, vr_enter, void(Racer thisveh, entity instance))
 {
 #ifdef SVQC
     set_movetype(instance, MOVETYPE_BOUNCE);
-    instance.owner.vehicle_health = (instance.vehicle_health / autocvar_g_vehicle_racer_health)  * 100;
+    instance.owner.vehicle_health = (GetResourceAmount(instance, RESOURCE_HEALTH) / autocvar_g_vehicle_racer_health)  * 100;
     instance.owner.vehicle_shield = (instance.vehicle_shield / autocvar_g_vehicle_racer_shield)  * 100;
 
     if(instance.owner.flagcarried)
@@ -544,7 +544,7 @@ METHOD(Racer, vr_spawn, void(Racer thisveh, entity instance))
 
     setthink(instance, racer_think);
     instance.nextthink   = time;
-    instance.vehicle_health = autocvar_g_vehicle_racer_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_racer_health);
     instance.vehicle_shield = autocvar_g_vehicle_racer_shield;
 
     set_movetype(instance, MOVETYPE_TOSS);
@@ -557,7 +557,7 @@ METHOD(Racer, vr_spawn, void(Racer thisveh, entity instance))
     instance.bouncefactor = autocvar_g_vehicle_racer_bouncefactor;
     instance.bouncestop = autocvar_g_vehicle_racer_bouncestop;
     instance.damageforcescale = 0.5;
-    instance.vehicle_health = autocvar_g_vehicle_racer_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_racer_health);
     instance.vehicle_shield = autocvar_g_vehicle_racer_shield;
 #endif
 }
@@ -625,9 +625,9 @@ METHOD(Racer, vr_setup, void(Racer thisveh, entity instance))
         instance.vehicle_flags |= VHF_HEALTHREGEN;
 
     instance.respawntime = autocvar_g_vehicle_racer_respawntime;
-    instance.vehicle_health = autocvar_g_vehicle_racer_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_racer_health);
     instance.vehicle_shield = autocvar_g_vehicle_racer_shield;
-    instance.max_health = instance.vehicle_health;
+    instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
 #endif
 
 #ifdef CSQC
index f44dcc578464e7a2e485c5da437a286bd721bc87..0150ee98f930fe673f2897d0592540c87981ff3e 100644 (file)
@@ -343,11 +343,11 @@ bool raptor_frame(entity this, float dt)
 
        vehicle_aimturret(vehic, trace_endpos, vehic.gun1, "fire1",
                                                  autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1,  autocvar_g_vehicle_raptor_cannon_pitchlimit_up,
-                                                 autocvar_g_vehicle_raptor_cannon_turnlimit * -1,  autocvar_g_vehicle_raptor_cannon_turnlimit,  autocvar_g_vehicle_raptor_cannon_turnspeed);
+                                                 autocvar_g_vehicle_raptor_cannon_turnlimit * -1,  autocvar_g_vehicle_raptor_cannon_turnlimit,  autocvar_g_vehicle_raptor_cannon_turnspeed, dt);
 
        vehicle_aimturret(vehic, trace_endpos, vehic.gun2, "fire1",
                                                  autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1,  autocvar_g_vehicle_raptor_cannon_pitchlimit_up,
-                                                 autocvar_g_vehicle_raptor_cannon_turnlimit * -1,  autocvar_g_vehicle_raptor_cannon_turnlimit,  autocvar_g_vehicle_raptor_cannon_turnspeed);
+                                                 autocvar_g_vehicle_raptor_cannon_turnlimit * -1,  autocvar_g_vehicle_raptor_cannon_turnlimit,  autocvar_g_vehicle_raptor_cannon_turnspeed, dt);
 
        /*
        ad = ad * 0.5;
@@ -369,7 +369,7 @@ bool raptor_frame(entity this, float dt)
                vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, dt, true);
 
        if(vehic.vehicle_flags  & VHF_HEALTHREGEN)
-               vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false);
+               vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false, RESOURCE_HEALTH);
 
        if(vehic.vehicle_flags  & VHF_ENERGYREGEN)
                vehicles_regen(vehic, vehic.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, dt, false);
@@ -427,7 +427,7 @@ bool raptor_frame(entity this, float dt)
        }
 
 
-       VEHICLE_UPDATE_PLAYER(this, vehic, health, raptor);
+       VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, raptor, RESOURCE_HEALTH);
        VEHICLE_UPDATE_PLAYER(this, vehic, energy, raptor);
        if(vehic.vehicle_flags & VHF_HASSHIELD)
                VEHICLE_UPDATE_PLAYER(this, vehic, shield, raptor);
@@ -471,7 +471,7 @@ bool raptor_takeoff(entity this, float dt)
                vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, dt, true);
 
        if(vehic.vehicle_flags  & VHF_HEALTHREGEN)
-               vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false);
+               vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, dt, false, RESOURCE_HEALTH);
 
        if(vehic.vehicle_flags  & VHF_ENERGYREGEN)
                vehicles_regen(vehic, vehic.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, dt, false);
@@ -481,7 +481,7 @@ bool raptor_takeoff(entity this, float dt)
        this.vehicle_reload2 = bound(0, vehic.bomb1.alpha * 100, 100);
        this.vehicle_ammo2 = (this.vehicle_reload2 == 100) ? 100 : 0;
 
-       VEHICLE_UPDATE_PLAYER(this, vehic, health, raptor);
+       VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, raptor, RESOURCE_HEALTH);
        VEHICLE_UPDATE_PLAYER(this, vehic, energy, raptor);
        if(vehic.vehicle_flags & VHF_HASSHIELD)
                VEHICLE_UPDATE_PLAYER(this, vehic, shield, raptor);
@@ -594,7 +594,7 @@ METHOD(Raptor, vr_enter, void(Raptor thisveh, entity instance))
     instance.owner.PlayerPhysplug = raptor_takeoff;
     set_movetype(instance, MOVETYPE_BOUNCEMISSILE);
     instance.solid               = SOLID_SLIDEBOX;
-    instance.owner.vehicle_health = (instance.vehicle_health / autocvar_g_vehicle_raptor_health) * 100;
+    instance.owner.vehicle_health = (GetResourceAmount(instance, RESOURCE_HEALTH) / autocvar_g_vehicle_raptor_health) * 100;
     instance.owner.vehicle_shield = (instance.vehicle_shield / autocvar_g_vehicle_raptor_shield) * 100;
     instance.velocity = '0 0 1'; // nudge upwards so takeoff sequence can work
     instance.tur_head.exteriormodeltoclient = instance.owner;
@@ -701,7 +701,7 @@ METHOD(Raptor, vr_spawn, void(Raptor thisveh, entity instance))
     }
 
     instance.frame               = 0;
-    instance.vehicle_health = autocvar_g_vehicle_raptor_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_raptor_health);
     instance.vehicle_shield = autocvar_g_vehicle_raptor_shield;
     set_movetype(instance, MOVETYPE_TOSS);
     instance.solid               = SOLID_SLIDEBOX;
@@ -720,7 +720,7 @@ METHOD(Raptor, vr_spawn, void(Raptor thisveh, entity instance))
     instance.bouncefactor = autocvar_g_vehicle_raptor_bouncefactor;
     instance.bouncestop = autocvar_g_vehicle_raptor_bouncestop;
     instance.damageforcescale = 0.25;
-    instance.vehicle_health = autocvar_g_vehicle_raptor_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_raptor_health);
     instance.vehicle_shield = autocvar_g_vehicle_raptor_shield;
 }
 METHOD(Raptor, vr_setup, void(Raptor thisveh, entity instance))
@@ -739,9 +739,9 @@ METHOD(Raptor, vr_setup, void(Raptor thisveh, entity instance))
 
     instance.vehicle_exit = raptor_exit;
     instance.respawntime = autocvar_g_vehicle_raptor_respawntime;
-    instance.vehicle_health = autocvar_g_vehicle_raptor_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_raptor_health);
     instance.vehicle_shield = autocvar_g_vehicle_raptor_shield;
-    instance.max_health = instance.vehicle_health;
+    instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
 
     if(!autocvar_g_vehicle_raptor_swim)
        instance.dphitcontentsmask |= DPCONTENTS_LIQUIDSMASK;
index 09d0eb2af9175c6012b0a903677483e2e7c6d0cc..323b025e0ebb5769d434004073a1fb04598176c5 100644 (file)
@@ -264,7 +264,7 @@ bool spiderbot_frame(entity this, float dt)
 
                        .entity weaponentity = weaponentities[0]; // TODO: unhardcode
                        fireBullet(this, weaponentity, v, v_forward, autocvar_g_vehicle_spiderbot_minigun_spread, autocvar_g_vehicle_spiderbot_minigun_solidpenetration,
-                               autocvar_g_vehicle_spiderbot_minigun_damage, autocvar_g_vehicle_spiderbot_minigun_force, DEATH_VH_SPID_MINIGUN.m_id, 0);
+                               autocvar_g_vehicle_spiderbot_minigun_damage, autocvar_g_vehicle_spiderbot_minigun_force, DEATH_VH_SPID_MINIGUN.m_id, EFFECT_BULLET);
 
                        sound (gun, CH_WEAPON_A, SND_UZI_FIRE, VOL_BASE, ATTEN_NORM);
                        //trailparticles(this, _particleeffectnum("spiderbot_minigun_trail"), v, trace_endpos);
@@ -294,7 +294,7 @@ bool spiderbot_frame(entity this, float dt)
                vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_spiderbot_shield, autocvar_g_vehicle_spiderbot_shield_regen_pause, autocvar_g_vehicle_spiderbot_shield_regen, dt, true);
 
        if(vehic.vehicle_flags  & VHF_HEALTHREGEN)
-               vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_spiderbot_health, autocvar_g_vehicle_spiderbot_health_regen_pause, autocvar_g_vehicle_spiderbot_health_regen, dt, false);
+               vehicles_regen_resource(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_spiderbot_health, autocvar_g_vehicle_spiderbot_health_regen_pause, autocvar_g_vehicle_spiderbot_health_regen, dt, false, RESOURCE_HEALTH);
 
        PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
        //this.vehicle_ammo2 = vehic.tur_head.frame;
@@ -309,7 +309,7 @@ bool spiderbot_frame(entity this, float dt)
        this.oldorigin = this.origin; // negate fall damage
        this.velocity = vehic.velocity;
 
-       VEHICLE_UPDATE_PLAYER(this, vehic, health, spiderbot);
+       VEHICLE_UPDATE_PLAYER_RESOURCE(this, vehic, health, spiderbot, RESOURCE_HEALTH);
 
        if(vehic.vehicle_flags & VHF_HASSHIELD)
                VEHICLE_UPDATE_PLAYER(this, vehic, shield, spiderbot);
@@ -524,7 +524,7 @@ METHOD(Spiderbot, vr_enter, void(Spiderbot thisveh, entity instance))
     STAT(VEHICLESTAT_W2MODE, instance) = SBRM_GUIDE;
     set_movetype(instance, MOVETYPE_WALK);
     CSQCVehicleSetup(instance.owner, 0);
-    instance.owner.vehicle_health = (instance.vehicle_health / autocvar_g_vehicle_spiderbot_health) * 100;
+    instance.owner.vehicle_health = (GetResourceAmount(instance, RESOURCE_HEALTH) / autocvar_g_vehicle_spiderbot_health) * 100;
     instance.owner.vehicle_shield = (instance.vehicle_shield / autocvar_g_vehicle_spiderbot_shield) * 100;
 
     if(instance.owner.flagcarried)
@@ -582,7 +582,7 @@ METHOD(Spiderbot, vr_spawn, void(Spiderbot thisveh, entity instance))
     setorigin(instance, instance.pos1 + '0 0 128');
     instance.angles = instance.pos2;
     instance.damageforcescale = 0.03;
-    instance.vehicle_health = autocvar_g_vehicle_spiderbot_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_spiderbot_health);
     instance.vehicle_shield = autocvar_g_vehicle_spiderbot_shield;
 
     instance.PlayerPhysplug = spiderbot_frame;
@@ -599,9 +599,9 @@ METHOD(Spiderbot, vr_setup, void(Spiderbot thisveh, entity instance))
         instance.vehicle_flags |= VHF_HEALTHREGEN;
 
     instance.respawntime = autocvar_g_vehicle_spiderbot_respawntime;
-    instance.vehicle_health = autocvar_g_vehicle_spiderbot_health;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, autocvar_g_vehicle_spiderbot_health);
     instance.vehicle_shield = autocvar_g_vehicle_spiderbot_shield;
-    instance.max_health = instance.vehicle_health;
+    instance.max_health = GetResourceAmount(instance, RESOURCE_HEALTH);
     instance.pushable = true; // spiderbot can use jumppads
 }
 
index f2dab7bfd22cd7c8620f3b69e7c10d9f37ece242..e1be43af0d41f9826993c9b9a7e1dceb247b9e16 100644 (file)
@@ -48,7 +48,7 @@ void viewloc_PlayerPhysics(entity this)
                        if(PHYS_CS(this).movement_x > 0) // right
                                this.angles_y = forward.y;
                }
-
+       #if 0
                //if(!PHYS_INPUT_BUTTON_CROUCH(this) && !IS_DUCKED(this))
                if(!(this.viewloc.spawnflags & VIEWLOC_FREEMOVE))
                {
@@ -65,6 +65,7 @@ void viewloc_PlayerPhysics(entity this)
                        //else { input_buttons &= ~16; this.flags &= ~FL_DUCKED; }
 #endif
                }
+       #endif
        }
 }
 
index 9b4c0b46814308dfb037375345a6100c14d444f4..9a32b4277839b32ddfdcb8368747615387556f06 100644 (file)
@@ -130,7 +130,7 @@ REGISTER_WEAPON(Null, NEW(Weapon));
 Weapon Weapons_fromstr(string s)
 {
     FOREACH(Weapons, it != WEP_Null && it.netname == s, return it);
-    return NULL;
+    return WEP_Null;
 }
 
 
index d2443f1670d360767cbc6721f6a2722d2764997c..b53e25fde9c95cbdaddc1b987fd30fa16f8f8d16 100644 (file)
@@ -25,7 +25,10 @@ float W_Config_Queue_Compare(int root, int child, entity pass)
 void Dump_Weapon_Settings()
 {
        int totalweapons = 0, totalsettings = 0;
+       int wepcount = 1;
        FOREACH(Weapons, it != WEP_Null, {
+               if((it.spawnflags & WEP_FLAG_HIDDEN) && (it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && !(it.spawnflags & WEP_FLAG_NORMAL))
+                       continue; // never include the attacks
                // step 1: clear the queue
                WEP_CONFIG_COUNT = 0;
                for (int x = 0; x <= MAX_CONFIG_SETTINGS; ++x)
@@ -40,7 +43,7 @@ void Dump_Weapon_Settings()
                // step 4: write queue
                WEP_CONFIG_WRITETOFILE(sprintf(
                        "// {{{ #%d: %s%s\n",
-                       i,
+                       wepcount,
                        it.m_name,
                        ((it.spawnflags & WEP_FLAG_MUTATORBLOCKED) ? " (MUTATOR WEAPON)" : "")
                ));
@@ -51,6 +54,7 @@ void Dump_Weapon_Settings()
                LOG_INFOF("#%d: %s: %d settings...", i, it.m_name, WEP_CONFIG_COUNT);
                totalweapons += 1;
                totalsettings += WEP_CONFIG_COUNT;
+               wepcount += 1;
        });
 
        // clear queue now that we're finished
index a3f2336a9487b275a8a130a031295c462072d77b..cdf5748e678bf62388ffd85c793cd9194f8656d1 100644 (file)
@@ -162,9 +162,9 @@ CLASS(WeaponPickup, Pickup)
     METHOD(WeaponPickup, giveTo, bool(entity this, entity item, entity player))
     {
         bool b = Item_GiveTo(item, player);
-        if (b) {
-            LOG_TRACEF("entity %i picked up %s", player, this.m_name);
-        }
+        //if (b) {
+            //LOG_TRACEF("entity %i picked up %s", player, this.m_name);
+        //}
         return b;
     }
 #endif
index e78e10a3641d772427a9c053170db903a70e2fb0..ef4e3eb7d7709e19b709396f0a30cf0d234820e4 100644 (file)
@@ -236,8 +236,8 @@ void W_Arc_Beam_Think(entity this)
                {
                        // note: this doesn't force the switch
                        W_SwitchToOtherWeapon(own, weaponentity);
-                       own.(weaponentity).arc_BUTTON_ATCK_prev = false; // hax
                }
+               own.(weaponentity).arc_BUTTON_ATCK_prev = false; // allow switching weapons
                delete(this);
                return;
        }
@@ -265,14 +265,14 @@ void W_Arc_Beam_Think(entity this)
 
        W_SetupShot_Range(
                own,
-               weaponentity, // TODO
+               weaponentity,
                true,
                0,
                SND_Null,
                0,
                WEP_CVAR(arc, beam_damage) * coefficient,
                WEP_CVAR(arc, beam_range),
-               WEP_ARC.m_id
+               thiswep.m_id
        );
 
        // After teleport, "lock" the beam until the teleport is confirmed.
@@ -322,7 +322,10 @@ void W_Arc_Beam_Think(entity this)
                                (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
                                min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
                        );
-                       this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
+                       if(vdist(this.beam_dir - w_shotdir, <, 0.01))
+                               this.beam_dir = w_shotdir;
+                       else
+                               this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
                }
                else
                {
@@ -332,7 +335,10 @@ void W_Arc_Beam_Think(entity this)
                                (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
                                1
                        );
-                       this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
+                       if(vdist(this.beam_dir - w_shotdir, <, 0.01))
+                               this.beam_dir = w_shotdir;
+                       else
+                               this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor));
                }
 
                // network information: beam direction
@@ -467,7 +473,7 @@ void W_Arc_Beam_Think(entity this)
                                {
                                        accuracy_add(
                                                own,
-                                               WEP_ARC.m_id,
+                                               WEP_ARC,
                                                0,
                                                rootdamage * coefficient * falloff
                                        );
@@ -533,17 +539,33 @@ void W_Arc_Beam(float burst, entity actor, .entity weaponentity)
 
        getthink(beam)(beam);
 }
-void Arc_Smoke(entity actor, .entity weaponentity)
+void W_Arc_Attack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
+{
+       if(!actor.(weaponentity).arc_beam || wasfreed(actor.(weaponentity).arc_beam))
+       {
+               w_ready(thiswep, actor, weaponentity, fire);
+               return;
+       }
+
+       // attack handled by the beam itself, this is just a loop to keep the attack happening!
+
+       // NOTE: arc doesn't use a refire
+       //ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(arc, refire) * W_WeaponRateFactor(actor);
+       actor.(weaponentity).wframe = WFRAME_FIRE1;
+       weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), W_Arc_Attack);
+}
+void Arc_Smoke(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
+       // TODO: spamming this without checking any refires is asking for trouble!
        makevectors(actor.v_angle);
-       W_SetupShot_Range(actor,weaponentity,true,0,SND_Null,0,0,0,WEP_ARC.m_id); // TODO: probably doesn't need deathtype, since this is just a prefire effect
+       W_SetupShot_Range(actor,weaponentity,false,0,SND_Null,0,0,0,thiswep.m_id); // TODO: probably doesn't need deathtype, since this is just a prefire effect
 
        vector smoke_origin = w_shotorg + actor.velocity*frametime;
        if ( actor.arc_overheat > time )
        {
                if ( random() < actor.(weaponentity).arc_heat_percent )
                        Send_Effect(EFFECT_ARC_SMOKE, smoke_origin, '0 0 0', 1 );
-               if ( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) )
+               if ( (fire & 1) || (fire & 2) )
                {
                        Send_Effect(EFFECT_ARC_OVERHEAT_FIRE, smoke_origin, w_shotdir, 1 );
                        if ( !actor.arc_smoke_sound )
@@ -562,7 +584,7 @@ void Arc_Smoke(entity actor, .entity weaponentity)
        }
 
        if (  actor.arc_smoke_sound && ( actor.arc_overheat <= time ||
-               !( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) ) ) || actor.(weaponentity).m_switchweapon != WEP_ARC )
+               !( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) ) ) || actor.(weaponentity).m_switchweapon != thiswep )
        {
                actor.arc_smoke_sound = 0;
                sound(actor, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
@@ -597,14 +619,14 @@ METHOD(Arc, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 METHOD(Arc, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
     Arc_Player_SetHeat(actor, weaponentity);
-    Arc_Smoke(actor, weaponentity);
+    Arc_Smoke(thiswep, actor, weaponentity, fire);
 
     bool beam_fire2 = ((fire & 2) && !WEP_CVAR(arc, bolt));
 
     if (time >= actor.arc_overheat)
     if ((fire & 1) || beam_fire2 || actor.(weaponentity).arc_beam.beam_bursting)
     {
-
+       #if 0
         if(actor.(weaponentity).arc_BUTTON_ATCK_prev)
         {
             #if 0
@@ -614,6 +636,7 @@ METHOD(Arc, wr_think, void(entity thiswep, entity actor, .entity weaponentity, i
             #endif
                 weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), w_ready);
         }
+        #endif
 
         if((!actor.(weaponentity).arc_beam) || wasfreed(actor.(weaponentity).arc_beam))
         {
@@ -623,7 +646,8 @@ METHOD(Arc, wr_think, void(entity thiswep, entity actor, .entity weaponentity, i
 
                 if(!actor.(weaponentity).arc_BUTTON_ATCK_prev)
                 {
-                    weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
+                       actor.(weaponentity).wframe = WFRAME_FIRE1;
+                    weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), W_Arc_Attack);
                     actor.(weaponentity).arc_BUTTON_ATCK_prev = true;
                 }
             }
@@ -642,10 +666,9 @@ METHOD(Arc, wr_think, void(entity thiswep, entity actor, .entity weaponentity, i
 
     if(actor.(weaponentity).arc_BUTTON_ATCK_prev)
     {
-       int slot = weaponslot(weaponentity);
         sound(actor, CH_WEAPON_A, SND_ARC_STOP, VOL_BASE, ATTN_NORM);
         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
-        ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(actor);
+        ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(actor);
     }
     actor.(weaponentity).arc_BUTTON_ATCK_prev = false;
 
index 5a41666bdb74ba1a58b4a09eae9adf341e2a7d8a..064668ca6cafa443c2260cf00c11d8e9ce51c643 100644 (file)
@@ -297,7 +297,7 @@ void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        if(WEP_CVAR_PRI(crylink, joinexplode))
                maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
 
-       W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, WEP_CRYLINK.m_id);
+       W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
        forward = v_forward;
        right = v_right;
        up = v_up;
@@ -336,7 +336,7 @@ void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
                set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
                PROJECTILE_MAKETRIGGER(proj);
-               proj.projectiledeathtype = WEP_CRYLINK.m_id;
+               proj.projectiledeathtype = thiswep.m_id;
                //proj.gravity = 0.001;
 
                setorigin(proj, w_shotorg);
@@ -409,7 +409,7 @@ void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        if(WEP_CVAR_SEC(crylink, joinexplode))
                maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
 
-       W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, WEP_CRYLINK.m_id | HITTYPE_SECONDARY);
+       W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
        forward = v_forward;
        right = v_right;
        up = v_up;
@@ -448,7 +448,7 @@ void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 
                set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
                PROJECTILE_MAKETRIGGER(proj);
-               proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
+               proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
                //proj.gravity = 0.001;
 
                setorigin(proj, w_shotorg);
@@ -571,7 +571,7 @@ METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentit
             if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
             {
                 // ran out of ammo!
-                actor.cnt = WEP_CRYLINK.m_id;
+                actor.cnt = thiswep.m_id;
                 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
             }
         }
@@ -584,7 +584,7 @@ METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weapon
         return true;
 
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
     return ammo_amount;
 }
 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
@@ -594,7 +594,7 @@ METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon
         return true;
 
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
     return ammo_amount;
 }
 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
index ab97e3d7f0b6c5c16ba538092b3ece2bd2aeb4df..f8539b14c36b7eeb800830de893c625581e06337 100644 (file)
@@ -49,9 +49,8 @@ void W_Devastator_Explode(entity this, entity directhitentity)
                if(GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
                if(!(this.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
                {
-                       this.realowner.cnt = WEP_DEVASTATOR.m_id;
-                       int slot = weaponslot(weaponentity);
-                       ATTACK_FINISHED(this.realowner, slot) = time;
+                       this.realowner.cnt = thiswep.m_id;
+                       ATTACK_FINISHED(this.realowner, weaponentity) = time;
                        this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
                }
        }
@@ -143,9 +142,8 @@ void W_Devastator_DoRemoteExplode(entity this, .entity weaponentity)
                if(GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(devastator, ammo))
                if(!(this.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
                {
-                       this.realowner.cnt = WEP_DEVASTATOR.m_id;
-                       int slot = weaponslot(weaponentity);
-                       ATTACK_FINISHED(this.realowner, slot) = time;
+                       this.realowner.cnt = thiswep.m_id;
+                       ATTACK_FINISHED(this.realowner, weaponentity) = time;
                        this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
                }
        }
@@ -306,7 +304,7 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int
 {
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR(devastator, ammo), weaponentity);
 
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage), WEP_DEVASTATOR.m_id);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage), thiswep.m_id);
        Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
        entity missile = WarpZone_RefSys_SpawnSameRefSys(actor);
@@ -331,7 +329,7 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int
 
        set_movetype(missile, MOVETYPE_FLY);
        PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_DEVASTATOR.m_id;
+       missile.projectiledeathtype = thiswep.m_id;
        setsize(missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
 
        setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
@@ -464,7 +462,7 @@ METHOD(Devastator, wr_think, void(entity thiswep, entity actor, .entity weaponen
             actor.(weaponentity).rl_release = 1;
 
         if(fire & 2)
-        if(actor.(weaponentity).m_switchweapon == WEP_DEVASTATOR)
+        if(actor.(weaponentity).m_switchweapon == thiswep)
         {
             bool rockfound = false;
             IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
@@ -488,7 +486,7 @@ METHOD(Devastator, wr_checkammo1, bool(entity thiswep, entity actor, .entity wea
 {
     #if 0
     // don't switch while guiding a missile
-    if(ATTACK_FINISHED(actor, slot) <= time || PS(actor).m_weapon != WEP_DEVASTATOR)
+    if(ATTACK_FINISHED(actor, weaponentity) <= time || PS(actor).m_weapon != WEP_DEVASTATOR)
     {
         ammo_amount = false;
         if(WEP_CVAR(devastator, reload_ammo))
@@ -516,7 +514,7 @@ METHOD(Devastator, wr_checkammo1, bool(entity thiswep, entity actor, .entity wea
     }
     #else
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(devastator, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(devastator, ammo);
     return ammo_amount;
     #endif
 }
index 89738289f2b84b0205020e9ae0b4f33eeca05afb..e22b5e941270b7f2757b407675b6db7c6ead44a5 100644 (file)
@@ -205,7 +205,7 @@ void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
                SND_ELECTRO_FIRE,
                CH_WEAPON_A,
                WEP_CVAR_PRI(electro, damage),
-               WEP_ELECTRO.m_id
+               thiswep.m_id
        );
 
        Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
@@ -219,7 +219,7 @@ void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
        proj.nextthink = time;
        proj.ltime = time + WEP_CVAR_PRI(electro, lifetime);
        PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_ELECTRO.m_id;
+       proj.projectiledeathtype = thiswep.m_id;
        proj.weaponentity_fld = weaponentity;
        setorigin(proj, w_shotorg);
 
@@ -279,14 +279,14 @@ void W_Electro_Orb_Stick(entity this, entity to)
        delete(this);
 
        if(to)
-               SetMovetypeFollow(this, to);
+               SetMovetypeFollow(newproj, to);
 }
 
 void W_Electro_Orb_Touch(entity this, entity toucher)
 {
        PROJECTILE_TOUCH(this, toucher);
-       if(toucher.takedamage == DAMAGE_AIM)
-               { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this, toucher); } }
+       if(toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(electro, touchexplode))
+               { W_Electro_Explode(this, toucher); }
        else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles!
        {
                //UpdateCSQCProjectile(this);
@@ -354,7 +354,7 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
                SND_ELECTRO_FIRE2,
                CH_WEAPON_A,
                WEP_CVAR_SEC(electro, damage),
-               WEP_ELECTRO.m_id | HITTYPE_SECONDARY
+               thiswep.m_id | HITTYPE_SECONDARY
        );
 
        w_shotdir = v_forward; // no TrueAim for grenades please
@@ -369,7 +369,7 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
        proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage);
        proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime);
        PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_SECONDARY;
+       proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
        proj.weaponentity_fld = weaponentity;
        setorigin(proj, w_shotorg);
 
@@ -394,14 +394,6 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
        proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
        proj.missile_flags = MIF_SPLASH | MIF_ARC;
 
-#if 0
-       entity p2;
-       p2 = spawn();
-       copyentity(proj, p2);
-       setmodel(p2, MDL_PROJECTILE_ELECTRO);
-       setsize(p2, proj.mins, proj.maxs);
-#endif
-
        CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
 
        MUTATOR_CALLHOOK(EditProjectile, actor, proj);
@@ -413,7 +405,7 @@ void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, i
        if(PHYS_INPUT_BUTTON_ATCK2(actor))
        if(weapon_prepareattack(thiswep, actor, weaponentity, true, -1))
        {
-               W_Electro_Attack_Orb(WEP_ELECTRO, actor, weaponentity);
+               W_Electro_Attack_Orb(thiswep, actor, weaponentity);
                actor.(weaponentity).electro_count -= 1;
                weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
                return;
@@ -492,7 +484,7 @@ METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentit
 METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(electro, ammo);
     return ammo_amount;
 }
 METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
@@ -501,12 +493,12 @@ METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon
     if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
     {
         ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
     }
     else
     {
         ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo);
     }
     return ammo_amount;
 }
index 84113b7020f210352d85360cfc5f6b0d6d488d61..ea7f5b8aea19e1031d1506847a0a117940701f1c 100644 (file)
@@ -42,7 +42,7 @@ void W_Fireball_Explode(entity this, entity directhitentity)
                        dir = normalize(e.origin + e.view_ofs - this.origin);
 
                        if(accuracy_isgooddamage(this.realowner, e))
-                               accuracy_add(this.realowner, WEP_FIREBALL.m_id, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
+                               accuracy_add(this.realowner, WEP_FIREBALL, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
 
                        Damage(e, this, this.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, this.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, this.weaponentity_fld, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir);
                        Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1);
index 03855e316e7c1c9f4be758095ad057c06fa5c835..58be8f835f490ff3eac82c615a8257c8d047c27a 100644 (file)
@@ -81,7 +81,7 @@ void W_Hagar_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(hagar, ammo), weaponentity);
 
-       W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage), WEP_HAGAR.m_id);
+       W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage), thiswep.m_id);
 
        Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
@@ -102,7 +102,7 @@ void W_Hagar_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        setthink(missile, adaptor_think2use_hittype_splash);
        missile.nextthink = time + WEP_CVAR_PRI(hagar, lifetime);
        PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_HAGAR.m_id;
+       missile.projectiledeathtype = thiswep.m_id;
        missile.weaponentity_fld = weaponentity;
        setorigin(missile, w_shotorg);
        setsize(missile, '0 0 0', '0 0 0');
@@ -127,7 +127,7 @@ void W_Hagar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(hagar, ammo), weaponentity);
 
-       W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage), WEP_HAGAR.m_id | HITTYPE_SECONDARY);
+       W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage), thiswep.m_id | HITTYPE_SECONDARY);
 
        Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
@@ -149,7 +149,7 @@ void W_Hagar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        setthink(missile, adaptor_think2use_hittype_splash);
        missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
        PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
+       missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
        missile.weaponentity_fld = weaponentity;
        setorigin(missile, w_shotorg);
        setsize(missile, '0 0 0', '0 0 0');
@@ -169,7 +169,7 @@ void W_Hagar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 }
 
 .float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
-void W_Hagar_Attack2_Load_Release(entity actor, .entity weaponentity)
+void W_Hagar_Attack2_Load_Release(Weapon thiswep, entity actor, .entity weaponentity)
 {
        // time to release the rockets we've loaded
 
@@ -184,7 +184,7 @@ void W_Hagar_Attack2_Load_Release(entity actor, .entity weaponentity)
        weapon_prepareattack_do(actor, weaponentity, true, WEP_CVAR_SEC(hagar, refire));
 
        shots = actor.(weaponentity).hagar_load;
-       W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage) * shots, WEP_HAGAR.m_id | HITTYPE_SECONDARY);
+       W_SetupShot(actor, weaponentity, false, 2, SND_HAGAR_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage) * shots, thiswep.m_id | HITTYPE_SECONDARY);
        Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
        forward = v_forward;
@@ -211,7 +211,7 @@ void W_Hagar_Attack2_Load_Release(entity actor, .entity weaponentity)
                setthink(missile, adaptor_think2use_hittype_splash);
                missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
                PROJECTILE_MAKETRIGGER(missile);
-               missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
+               missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
                missile.weaponentity_fld = weaponentity;
                setorigin(missile, w_shotorg);
                setsize(missile, '0 0 0', '0 0 0');
@@ -257,6 +257,8 @@ void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
        // loadable hagar secondary attack, must always run each frame
        if(time < game_starttime || time < actor.race_penalty || timeout_status == TIMEOUT_ACTIVE)
                return;
+       if (round_handler_IsActive() && !round_handler_IsRoundStarted())
+               return;
 
        bool loaded = actor.(weaponentity).hagar_load >= WEP_CVAR_SEC(hagar, load_max);
 
@@ -265,7 +267,7 @@ void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
        if(actor.items & IT_UNLIMITED_WEAPON_AMMO)
                enough_ammo = true;
        else if(autocvar_g_balance_hagar_reload_ammo)
-               enough_ammo = actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
+               enough_ammo = actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
        else
                enough_ammo = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hagar, ammo);
 
@@ -340,7 +342,7 @@ void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
                if(!PHYS_INPUT_BUTTON_ATCK2(actor) || (stopped && actor.(weaponentity).hagar_loadstep < time && WEP_CVAR_SEC(hagar, load_hold) >= 0))
                {
                        actor.(weaponentity).state = WS_READY;
-                       W_Hagar_Attack2_Load_Release(actor, weaponentity);
+                       W_Hagar_Attack2_Load_Release(thiswep, actor, weaponentity);
                }
        }
        else
@@ -361,7 +363,7 @@ void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
 
 void W_Hagar_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
-       if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != WEP_HAGAR || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
+       if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != thiswep || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
        {
                w_ready(thiswep, actor, weaponentity, fire);
                return;
@@ -377,16 +379,9 @@ void W_Hagar_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int
 
        W_Hagar_Attack(thiswep, actor, weaponentity);
 
-       int slot = weaponslot(weaponentity);
-       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(hagar, refire) * W_WeaponRateFactor(actor);
-       int theframe = WFRAME_FIRE1;
-       entity this = actor.(weaponentity);
-       if(this)
-       {
-               if(this.wframe == WFRAME_FIRE1)
-                       theframe = WFRAME_DONTCHANGE;
-       }
-       weapon_thinkf(actor, weaponentity, theframe, WEP_CVAR_PRI(hagar, refire), W_Hagar_Attack_Auto);
+       ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(hagar, refire) * W_WeaponRateFactor(actor);
+       actor.(weaponentity).wframe = WFRAME_FIRE1;
+       weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR_PRI(hagar, refire), W_Hagar_Attack_Auto);
 }
 
 METHOD(Hagar, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
@@ -426,7 +421,7 @@ METHOD(Hagar, wr_gonethink, void(entity thiswep, entity actor, .entity weaponent
     if(actor.(weaponentity).hagar_load)
     {
        actor.(weaponentity).state = WS_READY;
-       W_Hagar_Attack2_Load_Release(actor, weaponentity);
+       W_Hagar_Attack2_Load_Release(thiswep, actor, weaponentity);
     }
 }
 METHOD(Hagar, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
@@ -441,13 +436,13 @@ METHOD(Hagar, wr_setup, void(entity thiswep, entity actor, .entity weaponentity)
 METHOD(Hagar, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hagar, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
     return ammo_amount;
 }
 METHOD(Hagar, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hagar, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
     return ammo_amount;
 }
 METHOD(Hagar, wr_resetplayer, void(entity thiswep, entity actor))
@@ -462,7 +457,7 @@ METHOD(Hagar, wr_playerdeath, void(entity thiswep, entity actor, .entity weapone
 {
     // if we have any rockets loaded when we die, release them
     if(actor.(weaponentity).hagar_load && WEP_CVAR_SEC(hagar, load_releasedeath))
-       W_Hagar_Attack2_Load_Release(actor, weaponentity);
+       W_Hagar_Attack2_Load_Release(thiswep, actor, weaponentity);
 }
 METHOD(Hagar, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
 {
index 8f501653869caad8908a2723ad435dbfd70ff243..816ddae3655d523387571a2e9e23b850881be710 100644 (file)
@@ -30,7 +30,7 @@ void W_HLAC_Attack(Weapon thiswep, entity actor, .entity weaponentity)
     if(actor.crouch)
         spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod);
 
-       W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage), WEP_HLAC.m_id);
+       W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage), thiswep.m_id);
        Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
        if(!autocvar_g_norecoil)
        {
@@ -61,7 +61,7 @@ void W_HLAC_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        missile.flags = FL_PROJECTILE;
        IL_PUSH(g_projectiles, missile);
        IL_PUSH(g_bot_dodge, missile);
-       missile.projectiledeathtype = WEP_HLAC.m_id;
+       missile.projectiledeathtype = thiswep.m_id;
        missile.weaponentity_fld = weaponentity;
 
        CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
@@ -69,7 +69,7 @@ void W_HLAC_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        MUTATOR_CALLHOOK(EditProjectile, actor, missile);
 }
 
-void W_HLAC_Attack2(entity actor, .entity weaponentity)
+void W_HLAC_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 {
        entity missile;
     float spread;
@@ -80,7 +80,7 @@ void W_HLAC_Attack2(entity actor, .entity weaponentity)
     if(actor.crouch)
         spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod);
 
-       W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage), WEP_HLAC.m_id | HITTYPE_SECONDARY);
+       W_SetupShot(actor, weaponentity, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage), thiswep.m_id | HITTYPE_SECONDARY);
        Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
        missile = new(hlacbolt);
@@ -107,7 +107,7 @@ void W_HLAC_Attack2(entity actor, .entity weaponentity)
        IL_PUSH(g_projectiles, missile);
        IL_PUSH(g_bot_dodge, missile);
        missile.missile_flags = MIF_SPLASH;
-       missile.projectiledeathtype = WEP_HLAC.m_id | HITTYPE_SECONDARY;
+       missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
        missile.weaponentity_fld = weaponentity;
 
        CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
@@ -134,9 +134,8 @@ void W_HLAC_Attack_Frame(Weapon thiswep, entity actor, .entity weaponentity, int
                        return;
                }
 
-               int slot = weaponslot(weaponentity);
-               ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor(actor);
-               W_HLAC_Attack(WEP_HLAC, actor, weaponentity);
+               ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor(actor);
+               W_HLAC_Attack(thiswep, actor, weaponentity);
                actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame);
        }
@@ -153,7 +152,7 @@ void W_HLAC_Attack2_Frame(Weapon thiswep, entity actor, .entity weaponentity)
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(hlac, ammo), weaponentity);
 
     for(i=WEP_CVAR_SEC(hlac, shots);i>0;--i)
-        W_HLAC_Attack2(actor, weaponentity);
+        W_HLAC_Attack2(thiswep, actor, weaponentity);
 
        if(!autocvar_g_norecoil)
        {
@@ -192,13 +191,13 @@ METHOD(HLAC, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
 METHOD(HLAC, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hlac, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
     return ammo_amount;
 }
 METHOD(HLAC, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hlac, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
     return ammo_amount;
 }
 METHOD(HLAC, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
index 6319dc36ae3ba04b6c5324e6bb6743e547b47e86..607f1fcddfedfda1e80b7048aa0729fd7b22569d 100644 (file)
@@ -56,14 +56,13 @@ void W_MachineGun_Attack(Weapon thiswep, int deathtype, entity actor, .entity we
                actor.punchangle_x = random() - 0.5;
                actor.punchangle_y = random() - 0.5;
        }
-       int slot = weaponslot(weaponentity);
        // this attack_finished just enforces a cooldown at the end of a burst
-       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
+       ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
 
        if(actor.(weaponentity).misc_bulletcounter == 1)
-               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0);
+               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, EFFECT_BULLET);
        else
-               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, 0);
+               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, EFFECT_BULLET);
 
        Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
@@ -101,7 +100,7 @@ void W_MachineGun_Attack_Frame(Weapon thiswep, entity actor, .entity weaponentit
                        return;
                }
                actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
-               W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id, actor, weaponentity);
+               W_MachineGun_Attack(thiswep, thiswep.m_id, actor, weaponentity);
                weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
        }
        else
@@ -127,9 +126,9 @@ void W_MachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity
                return;
        }
 
-       W_DecreaseAmmo(WEP_MACHINEGUN, actor, WEP_CVAR(machinegun, sustained_ammo), weaponentity);
+       W_DecreaseAmmo(thiswep, actor, WEP_CVAR(machinegun, sustained_ammo), weaponentity);
 
-       W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), WEP_MACHINEGUN.m_id);
+       W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), thiswep.m_id);
        if(!autocvar_g_norecoil)
        {
                actor.punchangle_x = random() - 0.5;
@@ -137,7 +136,7 @@ void W_MachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity
        }
 
        machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR(machinegun, spread_max));
-       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
+       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), thiswep.m_id, EFFECT_BULLET);
 
        actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
 
@@ -152,21 +151,20 @@ void W_MachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity
                SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
        }
 
-       int slot = weaponslot(weaponentity);
-       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
+       ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto);
 }
 
 void W_MachineGun_Attack_Burst(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
-       W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), WEP_MACHINEGUN.m_id);
+       W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage), thiswep.m_id);
        if(!autocvar_g_norecoil)
        {
                actor.punchangle_x = random() - 0.5;
                actor.punchangle_y = random() - 0.5;
        }
 
-       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
+       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), thiswep.m_id, EFFECT_BULLET);
 
        Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
@@ -182,8 +180,7 @@ void W_MachineGun_Attack_Burst(Weapon thiswep, entity actor, .entity weaponentit
        actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
        if(actor.(weaponentity).misc_bulletcounter == 0)
        {
-               int slot = weaponslot(weaponentity);
-               ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(actor);
+               ATTACK_FINISHED(actor, weaponentity) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(actor);
                weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready);
        }
        else
@@ -238,7 +235,7 @@ METHOD(MachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponen
         if(weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
         {
             actor.(weaponentity).misc_bulletcounter = 1;
-            W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id, actor, weaponentity); // sets attack_finished
+            W_MachineGun_Attack(thiswep, thiswep.m_id, actor, weaponentity); // sets attack_finished
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
         }
 
@@ -246,7 +243,7 @@ METHOD(MachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponen
         if(weapon_prepareattack(thiswep, actor, weaponentity, true, 0))
         {
             actor.(weaponentity).misc_bulletcounter = 1;
-            W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY, actor, weaponentity); // sets attack_finished
+            W_MachineGun_Attack(thiswep, thiswep.m_id | HITTYPE_SECONDARY, actor, weaponentity); // sets attack_finished
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready);
         }
     }
@@ -262,9 +259,9 @@ METHOD(MachineGun, wr_checkammo1, bool(entity thiswep, entity actor, .entity wea
     if(WEP_CVAR(machinegun, reload_ammo))
     {
         if(WEP_CVAR(machinegun, mode) == 1)
-            ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
+            ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
         else
-            ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
+            ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, first_ammo);
     }
     return ammo_amount;
 }
@@ -279,9 +276,9 @@ METHOD(MachineGun, wr_checkammo2, bool(entity thiswep, entity actor, .entity wea
     if(WEP_CVAR(machinegun, reload_ammo))
     {
         if(WEP_CVAR(machinegun, mode) == 1)
-            ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
+            ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
         else
-            ammo_amount += actor.(weaponentity).(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
+            ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(machinegun, first_ammo);
     }
     return ammo_amount;
 }
index 6d3270a2b22788e6539f82115f93d72a2ef8b18c..6063c666eb5093b0aea9ba9af3b17730948f342d 100644 (file)
@@ -67,19 +67,17 @@ void W_MineLayer_Explode(entity this, entity directhitentity)
        RadiusDamage(this, this.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), NULL, NULL, WEP_CVAR(minelayer, force), this.projectiledeathtype, this.weaponentity_fld, directhitentity);
 
        .entity weaponentity = this.weaponentity_fld;
-       if(this.realowner.(weaponentity).m_weapon == WEP_MINE_LAYER)
+       Weapon thiswep = WEP_MINE_LAYER;
+       if(this.realowner.(weaponentity).m_weapon == thiswep)
        {
                entity own = this.realowner;
-               Weapon w = WEP_MINE_LAYER;
-               if(!w.wr_checkammo1(w, own, weaponentity))
+               if(!thiswep.wr_checkammo1(thiswep, own, weaponentity))
                {
-                       own.cnt = WEP_MINE_LAYER.m_id;
-                       int slot = weaponslot(weaponentity);
-                       ATTACK_FINISHED(own, slot) = time;
+                       own.cnt = thiswep.m_id;
+                       ATTACK_FINISHED(own, weaponentity) = time;
                        own.(weaponentity).m_switchweapon = w_getbestweapon(own, weaponentity);
                }
        }
-       this.realowner.(weaponentity).minelayer_mines -= 1;
        delete(this);
 }
 
@@ -100,19 +98,17 @@ void W_MineLayer_DoRemoteExplode(entity this)
                                                NULL, NULL, WEP_CVAR(minelayer, remote_force), this.projectiledeathtype | HITTYPE_BOUNCE, this.weaponentity_fld, NULL);
 
        .entity weaponentity = this.weaponentity_fld;
-       if(this.realowner.(weaponentity).m_weapon == WEP_MINE_LAYER)
+       Weapon thiswep = WEP_MINE_LAYER;
+       if(this.realowner.(weaponentity).m_weapon == thiswep)
        {
                entity own = this.realowner;
-               Weapon w = WEP_MINE_LAYER;
-               if(!w.wr_checkammo1(w, own, weaponentity))
+               if(!thiswep.wr_checkammo1(thiswep, own, weaponentity))
                {
-                       own.cnt = WEP_MINE_LAYER.m_id;
-                       int slot = weaponslot(weaponentity);
-                       ATTACK_FINISHED(own, slot) = time;
+                       own.cnt = thiswep.m_id;
+                       ATTACK_FINISHED(own, weaponentity) = time;
                        own.(weaponentity).m_switchweapon = w_getbestweapon(own, weaponentity);
                }
        }
-       this.realowner.(weaponentity).minelayer_mines -= 1;
        delete(this);
 }
 
@@ -226,15 +222,7 @@ void W_MineLayer_Touch(entity this, entity toucher)
        if(this.move_movetype == MOVETYPE_NONE || this.move_movetype == MOVETYPE_FOLLOW)
                return; // we're already a stuck mine, why do we get called? TODO does this even happen?
 
-       if(WarpZone_Projectile_Touch(this, toucher))
-       {
-               if(wasfreed(this))
-               {
-                       .entity weaponentity = this.weaponentity_fld;
-                       this.realowner.(weaponentity).minelayer_mines -= 1;
-               }
-               return;
-       }
+       PROJECTILE_TOUCH(this, toucher);
 
        if((toucher && IS_PLAYER(toucher) && !IS_DEAD(toucher)) || toucher.owner == this.owner)
        {
@@ -272,7 +260,8 @@ void W_MineLayer_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        // scan how many mines we placed, and return if we reached our limit
        if(WEP_CVAR(minelayer, limit))
        {
-               if(actor.(weaponentity).minelayer_mines >= WEP_CVAR(minelayer, limit))
+               int minecount = W_MineLayer_Count(actor, weaponentity);
+               if(minecount >= WEP_CVAR(minelayer, limit))
                {
                        // the refire delay keeps this message from being spammed
                        Send_Notification(NOTIF_ONE, actor, MSG_MULTI, WEAPON_MINELAYER_LIMIT, WEP_CVAR(minelayer, limit));
@@ -283,7 +272,7 @@ void W_MineLayer_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR(minelayer, ammo), weaponentity);
 
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-4 -4 -4', '4 4 4', false, 5, SND_MINE_FIRE, CH_WEAPON_A, WEP_CVAR(minelayer, damage), WEP_MINE_LAYER.m_id);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-4 -4 -4', '4 4 4', false, 5, SND_MINE_FIRE, CH_WEAPON_A, WEP_CVAR(minelayer, damage), thiswep.m_id);
        Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
        mine = WarpZone_RefSys_SpawnSameRefSys(actor);
@@ -307,7 +296,7 @@ void W_MineLayer_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
        set_movetype(mine, MOVETYPE_TOSS);
        PROJECTILE_MAKETRIGGER(mine);
-       mine.projectiledeathtype = WEP_MINE_LAYER.m_id;
+       mine.projectiledeathtype = thiswep.m_id;
        mine.weaponentity_fld = weaponentity;
        setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
 
@@ -338,8 +327,6 @@ void W_MineLayer_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        // common properties
 
        MUTATOR_CALLHOOK(EditProjectile, actor, mine);
-
-       actor.(weaponentity).minelayer_mines = W_MineLayer_Count(actor, weaponentity);
 }
 
 bool W_MineLayer_PlacedMines(entity this, .entity weaponentity, bool detonate)
@@ -365,7 +352,8 @@ bool W_MineLayer_PlacedMines(entity this, .entity weaponentity, bool detonate)
 METHOD(MineLayer, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     // aim and decide to fire if appropriate
-    if(actor.(weaponentity).minelayer_mines >= WEP_CVAR(minelayer, limit))
+    int minecount = W_MineLayer_Count(actor, weaponentity);
+    if(minecount >= WEP_CVAR(minelayer, limit))
         PHYS_INPUT_BUTTON_ATCK(actor) = false;
     else
         PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), false);
@@ -448,6 +436,8 @@ METHOD(MineLayer, wr_aim, void(entity thiswep, entity actor, .entity weaponentit
 }
 METHOD(MineLayer, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
+       actor.(weaponentity).minelayer_mines = W_MineLayer_Count(actor, weaponentity);
+
     if(autocvar_g_balance_minelayer_reload_ammo && actor.(weaponentity).clip_load < WEP_CVAR(minelayer, ammo)) // forced reload
     {
         // not if we're holding the minelayer without enough ammo, but can detonate existing mines
@@ -472,12 +462,11 @@ METHOD(MineLayer, wr_think, void(entity thiswep, entity actor, .entity weaponent
 }
 METHOD(MineLayer, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
-    //int slot = 0; // TODO: unhardcode
     // actually do // don't switch while placing a mine
-    //if(ATTACK_FINISHED(actor, slot) <= time || PS(actor).m_weapon != WEP_MINE_LAYER)
+    //if(ATTACK_FINISHED(actor, weaponentity) <= time || PS(actor).m_weapon != WEP_MINE_LAYER)
     //{
         float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(minelayer, ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(minelayer, ammo);
         return ammo_amount;
     //}
     //return true;
index 51267e50d4f80ad7d2d1de58b156328a2bd967ca..6e2bc91cf98631be25a6a5b2bca7c8be659dbc02 100644 (file)
@@ -151,7 +151,7 @@ void W_Mortar_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 {
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(mortar, ammo), weaponentity);
 
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage), WEP_MORTAR.m_id);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage), thiswep.m_id);
        w_shotdir = v_forward; // no TrueAim for grenades please
 
        Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
@@ -164,7 +164,7 @@ void W_Mortar_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
        gren.bouncestop = WEP_CVAR(mortar, bouncestop);
        PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_MORTAR.m_id;
+       gren.projectiledeathtype = thiswep.m_id;
        gren.weaponentity_fld = weaponentity;
        setorigin(gren, w_shotorg);
        setsize(gren, '-3 -3 -3', '3 3 3');
@@ -203,7 +203,7 @@ void W_Mortar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(mortar, ammo), weaponentity);
 
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage), WEP_MORTAR.m_id | HITTYPE_SECONDARY);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 4, SND_GRENADE_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage), thiswep.m_id | HITTYPE_SECONDARY);
        w_shotdir = v_forward; // no TrueAim for grenades please
 
        Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
@@ -216,7 +216,7 @@ void W_Mortar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
        gren.bouncestop = WEP_CVAR(mortar, bouncestop);
        PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY;
+       gren.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
        gren.weaponentity_fld = weaponentity;
        setorigin(gren, w_shotorg);
        setsize(gren, '-3 -3 -3', '3 3 3');
@@ -325,13 +325,13 @@ METHOD(Mortar, wr_think, void(entity thiswep, entity actor, .entity weaponentity
 METHOD(Mortar, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(mortar, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
     return ammo_amount;
 }
 METHOD(Mortar, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(mortar, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
     return ammo_amount;
 }
 METHOD(Mortar, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
index 243f5bc34af0497394cfd9cf1c83ffe9739f991e..722171b9589555ffba0ce2a734153cf13245f7be 100644 (file)
@@ -198,11 +198,11 @@ void W_Porto_Touch(entity this, entity toucher)
        }
 }
 
-void W_Porto_Attack(entity actor, .entity weaponentity, float type)
+void W_Porto_Attack(Weapon thiswep, entity actor, .entity weaponentity, float type)
 {
        entity gren;
 
-       W_SetupShot(actor, weaponentity, false, 4, SND_PORTO_FIRE, CH_WEAPON_A, 0, WEP_PORTO.m_id); // TODO: does the deathtype even need to be set here? porto can't hurt people
+       W_SetupShot(actor, weaponentity, false, 4, SND_PORTO_FIRE, CH_WEAPON_A, 0, thiswep.m_id); // TODO: does the deathtype even need to be set here? porto can't hurt people
        // always shoot from the eye
        w_shotdir = v_forward;
        w_shotorg = actor.origin + actor.view_ofs + ((w_shotorg - actor.origin - actor.view_ofs) * v_forward) * v_forward;
@@ -270,7 +270,7 @@ METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weapone
         if(!actor.porto_forbidden)
         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
         {
-            W_Porto_Attack(actor, weaponentity, 0);
+            W_Porto_Attack(thiswep, actor, weaponentity, 0);
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
         }
 
@@ -279,7 +279,7 @@ METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weapone
         if(!actor.porto_forbidden)
         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(porto, refire)))
         {
-            W_Porto_Attack(actor, weaponentity, 1);
+            W_Porto_Attack(thiswep, actor, weaponentity, 1);
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready);
         }
     }
@@ -306,7 +306,7 @@ METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weapone
         if(!actor.porto_forbidden)
         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
         {
-            W_Porto_Attack(actor, weaponentity, -1);
+            W_Porto_Attack(thiswep, actor, weaponentity, -1);
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
         }
     }
index d6996042dc56967ebf02c29a946329ea40ce4323..d4d692ec70f0adeab3f7118961bd904dbb66d533 100644 (file)
@@ -19,7 +19,7 @@ void W_Rifle_FireBullet(Weapon thiswep, .entity weaponentity, float pSpread, flo
        }
 
        for(i = 0; i < pShots; ++i)
-               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE));
+               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EFFECT_RIFLE : EFFECT_RIFLE_WEAK));
 
        if(autocvar_g_casings >= 2)
     {
@@ -28,17 +28,17 @@ void W_Rifle_FireBullet(Weapon thiswep, .entity weaponentity, float pSpread, flo
     }
 }
 
-void W_Rifle_Attack(entity actor, .entity weaponentity)
+void W_Rifle_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 {
-       W_Rifle_FireBullet(WEP_RIFLE, weaponentity, WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), WEP_RIFLE.m_id, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), SND_CAMPINGRIFLE_FIRE, actor);
+       W_Rifle_FireBullet(thiswep, weaponentity, WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), thiswep.m_id, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), SND_CAMPINGRIFLE_FIRE, actor);
 }
 
-void W_Rifle_Attack2(entity actor, .entity weaponentity)
+void W_Rifle_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 {
-       W_Rifle_FireBullet(WEP_RIFLE, weaponentity, WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), WEP_RIFLE.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), SND_CAMPINGRIFLE_FIRE2, actor);
+       W_Rifle_FireBullet(thiswep, weaponentity, WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), thiswep.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), SND_CAMPINGRIFLE_FIRE2, actor);
 }
 
-.void(entity actor, .entity weaponentity) rifle_bullethail_attackfunc;
+.void(Weapon thiswep, entity actor, .entity weaponentity) rifle_bullethail_attackfunc;
 .WFRAME rifle_bullethail_frame;
 .float rifle_bullethail_animtime;
 .float rifle_bullethail_refire;
@@ -47,28 +47,27 @@ void W_Rifle_BulletHail_Continue(Weapon thiswep, entity actor, .entity weaponent
        float r, af;
 
        Weapon sw = actor.(weaponentity).m_switchweapon; // make it not detect weapon changes as reason to abort firing
-       int slot = weaponslot(weaponentity);
-       af = ATTACK_FINISHED(actor, slot);
+       af = ATTACK_FINISHED(actor, weaponentity);
        actor.(weaponentity).m_switchweapon = actor.(weaponentity).m_weapon;
-       ATTACK_FINISHED(actor, slot) = time;
+       ATTACK_FINISHED(actor, weaponentity) = time;
        r = weapon_prepareattack(thiswep, actor, weaponentity, actor.rifle_bullethail_frame == WFRAME_FIRE2, actor.rifle_bullethail_refire);
        if(actor.(weaponentity).m_switchweapon == actor.(weaponentity).m_weapon)
                actor.(weaponentity).m_switchweapon = sw;
        if(r)
        {
-               actor.rifle_bullethail_attackfunc(actor, weaponentity);
+               actor.rifle_bullethail_attackfunc(thiswep, actor, weaponentity);
                weapon_thinkf(actor, weaponentity, actor.rifle_bullethail_frame, actor.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue);
        }
        else
        {
-               ATTACK_FINISHED(actor, slot) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
+               ATTACK_FINISHED(actor, weaponentity) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
        }
 }
 
-void W_Rifle_BulletHail(entity actor, .entity weaponentity, float mode, void(entity actor, .entity weaponentity) AttackFunc, WFRAME fr, float animtime, float refire)
+void W_Rifle_BulletHail(Weapon thiswep, entity actor, .entity weaponentity, float mode, void(Weapon thiswep, entity actor, .entity weaponentity) AttackFunc, WFRAME fr, float animtime, float refire)
 {
        // if we get here, we have at least one bullet to fire
-       AttackFunc(actor, weaponentity);
+       AttackFunc(thiswep, actor, weaponentity);
        if(mode)
        {
                // continue hail
@@ -122,7 +121,7 @@ METHOD(Rifle, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
         if(time >= actor.(weaponentity).rifle_accumulator + WEP_CVAR_PRI(rifle, burstcost))
         {
             weapon_prepareattack_do(actor, weaponentity, false, WEP_CVAR_PRI(rifle, refire));
-            W_Rifle_BulletHail(actor, weaponentity, WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
+            W_Rifle_BulletHail(thiswep, actor, weaponentity, WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
             actor.(weaponentity).rifle_accumulator += WEP_CVAR_PRI(rifle, burstcost);
         }
         if(fire & 2)
@@ -137,7 +136,7 @@ METHOD(Rifle, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
                     if(time >= actor.(weaponentity).rifle_accumulator + WEP_CVAR_SEC(rifle, burstcost))
                     {
                         weapon_prepareattack_do(actor, weaponentity, true, WEP_CVAR_SEC(rifle, refire));
-                        W_Rifle_BulletHail(actor, weaponentity, WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
+                        W_Rifle_BulletHail(thiswep, actor, weaponentity, WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
                         actor.(weaponentity).rifle_accumulator += WEP_CVAR_SEC(rifle, burstcost);
                     }
                 }
@@ -148,13 +147,13 @@ METHOD(Rifle, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
 METHOD(Rifle, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(rifle, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
     return ammo_amount;
 }
 METHOD(Rifle, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(rifle, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
     return ammo_amount;
 }
 METHOD(Rifle, wr_resetplayer, void(entity thiswep, entity actor))
index 10080bbad275bd71b067ffcefcfd333b03998894..784276ef46d8b63f2b1ce4f6c0ad4bee6c3ce4a1 100644 (file)
@@ -169,7 +169,7 @@ void W_Seeker_Fire_Missile(Weapon thiswep, entity actor, .entity weaponentity, v
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR(seeker, missile_ammo), weaponentity);
 
        makevectors(actor.v_angle);
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_SEEKER_FIRE, CH_WEAPON_A, 0, ((m_target != NULL) ? WEP_SEEKER.m_id | HITTYPE_SECONDARY : WEP_SEEKER.m_id));
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_SEEKER_FIRE, CH_WEAPON_A, 0, ((m_target != NULL) ? thiswep.m_id | HITTYPE_SECONDARY : thiswep.m_id));
        w_shotorg += f_diff;
        Send_Effect(EFFECT_SEEKER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
@@ -197,9 +197,9 @@ void W_Seeker_Fire_Missile(Weapon thiswep, entity actor, .entity weaponentity, v
        //missile.think           = W_Seeker_Missile_Animate; // csqc projectiles.
 
        if(missile.enemy != NULL)
-               missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
+               missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
        else
-               missile.projectiledeathtype = WEP_SEEKER.m_id;
+               missile.projectiledeathtype = thiswep.m_id;
 
 
        setorigin(missile, w_shotorg);
@@ -266,7 +266,7 @@ void W_Seeker_Fire_Flac(Weapon thiswep, entity actor, .entity weaponentity)
                        f_diff = '+1.25 +3.75 0';
                        break;
        }
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_FLAC_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, flac_damage), WEP_SEEKER.m_id | HITTYPE_SECONDARY);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_FLAC_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, flac_damage), thiswep.m_id | HITTYPE_SECONDARY);
        w_shotorg += f_diff;
 
        Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
@@ -281,7 +281,7 @@ void W_Seeker_Fire_Flac(Weapon thiswep, entity actor, .entity weaponentity)
        missile.nextthink               = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand);
        missile.solid                   = SOLID_BBOX;
        set_movetype(missile, MOVETYPE_FLY);
-       missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
+       missile.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
        missile.weaponentity_fld = weaponentity;
        missile.flags = FL_PROJECTILE;
        IL_PUSH(g_projectiles, missile);
@@ -315,7 +315,7 @@ entity W_Seeker_Tagged_Info(entity isowner, .entity weaponentity, entity istarge
        return NULL;
 }
 
-void W_Seeker_Attack(entity actor, .entity weaponentity)
+void W_Seeker_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 {
        entity closest_target = NULL;
 
@@ -337,7 +337,7 @@ void W_Seeker_Attack(entity actor, .entity weaponentity)
                        closest_target = NULL;
        }
 
-       W_Seeker_Fire_Missile(WEP_SEEKER, actor, weaponentity, '0 0 0', closest_target);
+       W_Seeker_Fire_Missile(thiswep, actor, weaponentity, '0 0 0', closest_target);
 }
 
 void W_Seeker_Vollycontroller_Think(entity this) // TODO: Merge this with W_Seeker_Attack
@@ -348,7 +348,7 @@ void W_Seeker_Vollycontroller_Think(entity this) // TODO: Merge this with W_Seek
 
        Weapon thiswep = WEP_SEEKER;
        .entity weaponentity = this.weaponentity_fld;
-       if((!(this.realowner.items & IT_UNLIMITED_AMMO) && GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(seeker, missile_ammo)) || (this.cnt <= -1) || (IS_DEAD(this.realowner)) || (this.realowner.(weaponentity).m_switchweapon != WEP_SEEKER))
+       if((!(this.realowner.items & IT_UNLIMITED_AMMO) && GetResourceAmount(this.realowner, thiswep.ammo_type) < WEP_CVAR(seeker, missile_ammo)) || (this.cnt <= -1) || (IS_DEAD(this.realowner)) || (this.realowner.(weaponentity).m_switchweapon != thiswep))
        {
                delete(this);
                return;
@@ -365,17 +365,17 @@ void W_Seeker_Vollycontroller_Think(entity this) // TODO: Merge this with W_Seek
        switch(c)
        {
                case 0:
-                       W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '-1.25 -3.75 0', own.enemy); // TODO
+                       W_Seeker_Fire_Missile(thiswep, own, weaponentity, '-1.25 -3.75 0', own.enemy); // TODO
                        break;
                case 1:
-                       W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '+1.25 -3.75 0', own.enemy); // TODO
+                       W_Seeker_Fire_Missile(thiswep, own, weaponentity, '+1.25 -3.75 0', own.enemy); // TODO
                        break;
                case 2:
-                       W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '-1.25 +3.75 0', own.enemy); // TODO
+                       W_Seeker_Fire_Missile(thiswep, own, weaponentity, '-1.25 +3.75 0', own.enemy); // TODO
                        break;
                case 3:
                default:
-                       W_Seeker_Fire_Missile(WEP_SEEKER, own, weaponentity, '+1.25 +3.75 0', own.enemy); // TODO
+                       W_Seeker_Fire_Missile(thiswep, own, weaponentity, '+1.25 +3.75 0', own.enemy); // TODO
                        break;
        }
 
@@ -491,7 +491,7 @@ void W_Seeker_Fire_Tag(Weapon thiswep, entity actor, .entity weaponentity)
 {
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR(seeker, tag_ammo), weaponentity);
 
-       W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_TAG_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count), WEP_SEEKER.m_id | HITTYPE_BOUNCE | HITTYPE_SECONDARY);
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-2 -2 -2', '2 2 2', false, 2, SND_TAG_FIRE, CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count), thiswep.m_id | HITTYPE_BOUNCE | HITTYPE_SECONDARY);
 
        entity missile          = new(seeker_tag);
        missile.weaponentity_fld = weaponentity;
@@ -550,7 +550,7 @@ METHOD(Seeker, wr_think, void(entity thiswep, entity actor, .entity weaponentity
         {
             if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(seeker, missile_refire)))
             {
-                W_Seeker_Attack(actor, weaponentity);
+                W_Seeker_Attack(thiswep, actor, weaponentity);
                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready);
             }
         }
@@ -590,12 +590,12 @@ METHOD(Seeker, wr_checkammo1, bool(entity thiswep, entity actor, .entity weapone
     if(WEP_CVAR(seeker, type) == 1)
     {
         ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, missile_ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, missile_ammo);
     }
     else
     {
         ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, tag_ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, tag_ammo);
     }
     return ammo_amount;
 }
@@ -605,12 +605,12 @@ METHOD(Seeker, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapone
     if(WEP_CVAR(seeker, type) == 1)
     {
         ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, tag_ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, tag_ammo);
     }
     else
     {
         ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(seeker, flac_ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(seeker, flac_ammo);
     }
     return ammo_amount;
 }
index e9d55b24232353c0a5f086f5b40373f60b17f0c3..7e60667e5350bf1b8bb1c62bb19645df71d1ed42 100644 (file)
@@ -106,7 +106,7 @@ void W_Shockwave_Melee_Think(entity this)
 
                        // handle accuracy
                        if(accuracy_isgooddamage(this.realowner, target_victim))
-                               { accuracy_add(this.realowner, WEP_SHOCKWAVE.m_id, 0, swing_damage); }
+                               { accuracy_add(this.realowner, WEP_SHOCKWAVE, 0, swing_damage); }
 
                        #ifdef DEBUG_SHOCKWAVE
                        LOG_INFOF(
@@ -157,7 +157,7 @@ void W_Shockwave_Melee(Weapon thiswep, entity actor, .entity weaponentity, int f
        setthink(meleetemp, W_Shockwave_Melee_Think);
        meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor(actor);
        meleetemp.weaponentity_fld = weaponentity;
-       W_SetupShot_Range(actor, weaponentity, true, 0, SND_Null, 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range), WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY);
+       W_SetupShot_Range(actor, weaponentity, true, 0, SND_Null, 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range), thiswep.m_id | HITTYPE_SECONDARY);
 }
 
 // SHOCKWAVE ATTACK MODE
@@ -265,7 +265,7 @@ void W_Shockwave_Send(entity actor)
        WriteByte(MSG_BROADCAST, etof(actor));
 }
 
-void W_Shockwave_Attack(entity actor, .entity weaponentity)
+void W_Shockwave_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 {
        // declarations
        float multiplier, multiplier_from_accuracy, multiplier_from_distance;
@@ -276,7 +276,7 @@ void W_Shockwave_Attack(entity actor, .entity weaponentity)
        float i, queue = 0;
 
        // set up the shot direction
-       W_SetupShot(actor, weaponentity, true, 3, SND_LASERGUN_FIRE, CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage), WEP_SHOCKWAVE.m_id);
+       W_SetupShot(actor, weaponentity, true, 3, SND_LASERGUN_FIRE, CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage), thiswep.m_id);
        vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance)));
        WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, actor);
        vector attack_hitpos = trace_endpos;
@@ -292,7 +292,7 @@ void W_Shockwave_Attack(entity actor, .entity weaponentity)
                WEP_CVAR(shockwave, blast_splash_edgedamage),
                WEP_CVAR(shockwave, blast_splash_radius),
                w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
-               WEP_SHOCKWAVE.m_id,
+               thiswep.m_id,
                0,
                actor
        );
@@ -381,7 +381,7 @@ void W_Shockwave_Attack(entity actor, .entity weaponentity)
                                        actor,
                                        actor,
                                        final_damage,
-                                       WEP_SHOCKWAVE.m_id,
+                                       thiswep.m_id,
                                        weaponentity,
                                        head.origin,
                                        final_force
@@ -566,14 +566,14 @@ void W_Shockwave_Attack(entity actor, .entity weaponentity)
                        actor,
                        actor,
                        final_damage,
-                       WEP_SHOCKWAVE.m_id,
+                       thiswep.m_id,
                        weaponentity,
                        head.origin,
                        final_force
                );
 
                if(accuracy_isgooddamage(actor, head))
-                       accuracy_add(actor, WEP_SHOCKWAVE.m_id, 0, final_damage);
+                       accuracy_add(actor, thiswep, 0, final_damage);
 
                #ifdef DEBUG_SHOCKWAVE
                LOG_INFOF(
@@ -608,7 +608,7 @@ METHOD(Shockwave, wr_think, void(entity thiswep, entity actor, .entity weaponent
         {
             if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(shockwave, blast_animtime)))
             {
-                W_Shockwave_Attack(actor, weaponentity);
+                W_Shockwave_Attack(thiswep, actor, weaponentity);
                 actor.(weaponentity).shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor(actor);
                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready);
             }
index 4acceed7184c924af8ffca37017fe3b8794191b4..8faac3d5d03d7e2a4f2dd8f98e9303a572a36ea5 100644 (file)
@@ -2,14 +2,13 @@
 
 #ifdef SVQC
 
-void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force)
+void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force, entity bullet_trail_effect)
 {
        W_DecreaseAmmo(thiswep, actor, ammocount, weaponentity);
 
-       W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), damage * bullets, WEP_SHOTGUN.m_id);
+       W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), damage * bullets, thiswep.m_id);
        for(int sc = 0;sc < bullets;sc = sc + 1)
-               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, spread, solidpenetration, damage, force, WEP_SHOTGUN.m_id, 0);
-
+               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, spread, solidpenetration, damage, force, thiswep.m_id, bullet_trail_effect);
 
        Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, ammocount);
 
@@ -96,7 +95,7 @@ void W_Shotgun_Melee_Think(entity this)
                                this.realowner.origin + this.realowner.view_ofs,
                                v_forward * WEP_CVAR_SEC(shotgun, force));
 
-                       if(accuracy_isgooddamage(this.realowner, target_victim)) { accuracy_add(this.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); }
+                       if(accuracy_isgooddamage(this.realowner, target_victim)) { accuracy_add(this.realowner, WEP_SHOTGUN, 0, swing_damage); }
 
                        // draw large red flash for debugging
                        //te_customflash(targpos, 200, 2, '15 0 0');
@@ -153,13 +152,14 @@ void W_Shotgun_Attack3_Frame2(Weapon thiswep, entity actor, .entity weaponentity
        }
 
        sound(actor, CH_WEAPON_SINGLE, SND_Null, VOL_BASE, ATTN_NORM); // kill previous sound
-       W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, true,
+       W_Shotgun_Attack(thiswep, actor, weaponentity, true,
                WEP_CVAR_PRI(shotgun, ammo),
                WEP_CVAR_PRI(shotgun, damage),
                WEP_CVAR_PRI(shotgun, bullets),
                WEP_CVAR_PRI(shotgun, spread),
                WEP_CVAR_PRI(shotgun, solidpenetration),
-               WEP_CVAR_PRI(shotgun, force)); // actually is secondary, but we trick the last shot into playing full reload sound
+               WEP_CVAR_PRI(shotgun, force),
+               EFFECT_BULLET_WEAK); // actually is secondary, but we trick the last shot into playing full reload sound
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
 }
 void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity, int fire)
@@ -172,13 +172,14 @@ void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity
                return;
        }
 
-       W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, false,
+       W_Shotgun_Attack(thiswep, actor, weaponentity, false,
                WEP_CVAR_PRI(shotgun, ammo),
                WEP_CVAR_PRI(shotgun, damage),
                WEP_CVAR_PRI(shotgun, bullets),
                WEP_CVAR_PRI(shotgun, spread),
                WEP_CVAR_PRI(shotgun, solidpenetration),
-               WEP_CVAR_PRI(shotgun, force));
+               WEP_CVAR_PRI(shotgun, force),
+               EFFECT_BULLET_WEAK);
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
 }
 
@@ -215,7 +216,8 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
                                                WEP_CVAR_PRI(shotgun, bullets),
                                                WEP_CVAR_PRI(shotgun, spread),
                                                WEP_CVAR_PRI(shotgun, solidpenetration),
-                                               WEP_CVAR_PRI(shotgun, force));
+                                               WEP_CVAR_PRI(shotgun, force),
+                                               EFFECT_BULLET_WEAK);
                     actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(actor);
                     weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
                 }
@@ -233,7 +235,8 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
                                                WEP_CVAR_PRI(shotgun, bullets),
                                                WEP_CVAR_PRI(shotgun, spread),
                                                WEP_CVAR_PRI(shotgun, solidpenetration),
-                                               WEP_CVAR_PRI(shotgun, force));
+                                               WEP_CVAR_PRI(shotgun, force),
+                                               EFFECT_BULLET_WEAK);
                     actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor(actor);
                     weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
                 }
@@ -252,7 +255,7 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
 METHOD(Shotgun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
     return ammo_amount;
 }
 METHOD(Shotgun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
@@ -266,7 +269,7 @@ METHOD(Shotgun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon
         case 2: // secondary triple shot
         {
             float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(shotgun, ammo);
-            ammo_amount += actor.(weaponentity).(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
+            ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
             return ammo_amount;
         }
         default: return false; // secondary unavailable
index 2dd5cae1552e6434c95a6eddd0cdbfa9cb2cd226..cefa4558f2fd9b43f7aa2fa1a9fe94f8cd63582b 100644 (file)
@@ -105,7 +105,7 @@ NET_HANDLE(TE_CSQC_VAPORBEAMPARTICLE, bool isNew)
 void W_RocketMinsta_Explosion(entity actor, .entity weaponentity, vector loc)
 {
        if(accuracy_canbegooddamage(actor))
-               accuracy_add(actor, WEP_DEVASTATOR.m_id, autocvar_g_rm_damage, 0);
+               accuracy_add(actor, WEP_DEVASTATOR, autocvar_g_rm_damage, 0);
        entity dmgent = spawn();
        dmgent.owner = dmgent.realowner = actor;
        setorigin(dmgent, loc);
@@ -118,14 +118,14 @@ void W_Vaporizer_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        bool flying = IsFlying(actor); // do this BEFORE to make the trace values from FireRailgunBullet last
        float vaporizer_damage = ((WEP_CVAR_PRI(vaporizer, damage) > 0) ? WEP_CVAR_PRI(vaporizer, damage) : 10000);
 
-       W_SetupShot(actor, weaponentity, true, 0, SND_Null, CH_WEAPON_A, vaporizer_damage, WEP_VAPORIZER.m_id);
+       W_SetupShot(actor, weaponentity, true, 0, SND_Null, CH_WEAPON_A, vaporizer_damage, thiswep.m_id);
        // handle sound separately so we can change the volume
        // added bonus: no longer plays the strength sound (strength gives no bonus to instakill anyway)
        sound (actor, CH_WEAPON_A, SND_MINSTANEXFIRE, VOL_BASE * 0.8, ATTEN_NORM);
 
        yoda = 0;
        damage_goodhits = 0;
-       FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, vaporizer_damage, WEP_CVAR_PRI(vaporizer, force), 0, 0, 0, 0, WEP_VAPORIZER.m_id);
+       FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, vaporizer_damage, WEP_CVAR_PRI(vaporizer, force), 0, 0, 0, 0, thiswep.m_id);
 
        // do this now, as goodhits is disabled below
        SendCSQCVaporizerBeamParticle(actor, damage_goodhits);
@@ -351,7 +351,7 @@ METHOD(Vaporizer, wr_checkammo1, bool(entity thiswep, entity actor, .entity weap
 {
     float vaporizer_ammo = ((autocvar_g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo));
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= vaporizer_ammo;
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo;
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= vaporizer_ammo;
     return ammo_amount;
 }
 METHOD(Vaporizer, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
@@ -359,7 +359,7 @@ METHOD(Vaporizer, wr_checkammo2, bool(entity thiswep, entity actor, .entity weap
     if(!WEP_CVAR_SEC(vaporizer, ammo))
         return true;
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(vaporizer, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
+    ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
     return ammo_amount;
 }
 METHOD(Vaporizer, wr_resetplayer, void(entity thiswep, entity actor))
index b8963229710e23266cba03cb023c2a443773d6b1..2e9a60ab85157e68c9c946ea7857a318cd65579d 100644 (file)
@@ -105,7 +105,7 @@ void W_Vortex_Attack(Weapon thiswep, entity actor, .entity weaponentity, float i
        myforcehalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_forcehalflife);
        myammo = WEP_CVAR_BOTH(vortex, !issecondary, ammo);
 
-    float dtype = WEP_VORTEX.m_id;
+    float dtype = thiswep.m_id;
     if(WEP_CVAR_BOTH(vortex, !issecondary, armorpierce))
         dtype |= HITTYPE_ARMORPIERCE;
 
@@ -228,7 +228,7 @@ METHOD(Vortex, wr_think, void(entity thiswep, entity actor, .entity weaponentity
                                     {
                                         actor.(weaponentity).clip_load = max(WEP_CVAR_SEC(vortex, ammo), actor.(weaponentity).clip_load - WEP_CVAR_SEC(vortex, ammo) * dt);
                                     }
-                                    actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) = actor.(weaponentity).clip_load;
+                                    actor.(weaponentity).(weapon_load[thiswep.m_id]) = actor.(weaponentity).clip_load;
                                 }
                                 else
                                 {
@@ -269,7 +269,7 @@ METHOD(Vortex, wr_setup, void(entity thiswep, entity actor, .entity weaponentity
 METHOD(Vortex, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
     float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(vortex, ammo);
-    ammo_amount += (autocvar_g_balance_vortex_reload_ammo && actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
+    ammo_amount += (autocvar_g_balance_vortex_reload_ammo && actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
     return ammo_amount;
 }
 METHOD(Vortex, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
@@ -278,7 +278,7 @@ METHOD(Vortex, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapone
     {
         // don't allow charging if we don't have enough ammo
         float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(vortex, ammo);
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
+        ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
         return ammo_amount;
     }
     else
index 9488c4c2c6bc0927a3ba8b30eebeb96baf9a2ccc..6b1797c664e9b38fa36f7eee6d6526d2eea4cd5c 100644 (file)
@@ -20,7 +20,7 @@ MACRO_END
     \
     PROP(false, m_alpha, WEPENT_SET_NORMAL, \
        { WriteByte(chan, rint(bound(-1, 254 * this.m_alpha, 254) - -1)); }, \
-       { (viewmodels[this.m_wepent_slot]).alpha = (ReadByte() + -1) / 254; }) \
+       { (viewmodels[this.m_wepent_slot]).m_alpha = (ReadByte() + -1) / 254; }) \
     \
     PROP(false, vortex_charge, WEPENT_SET_NORMAL, \
        { WriteByte(chan, this.vortex_charge * 255); }, \
index d6db7745b9fe1cc5e1ebc8f0f0edcfca78382214..556f58194f5ed5298d52f0aad3f4e6ca3f046e2a 100644 (file)
@@ -35,6 +35,8 @@ REGISTER_NET_TEMP(CLIENT_WEPENT)
        .Weapon switchingweapon;
        .Weapon switchweapon;
 
+       .float m_alpha;
+
        // only for Porto
        .bool angles_held_status;
        .vector angles_held;
index f87f00033762b4fbe18f2d5305989941a191828b..268b591264208458dcada567c028fbdf102be052 100644 (file)
@@ -210,11 +210,11 @@ bool CSQCPlayer_IsLocalPlayer(entity this)
 /** Called once per CSQC_UpdateView() */
 void CSQCPlayer_SetCamera()
 {
-       const vector v0 = ((intermission && !autocvar_cl_movement_intermissionrunning) ? '0 0 0' : pmove_vel); // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
-       const float vh = PHYS_VIEWHEIGHT(NULL);
-       const vector pl_viewofs = PHYS_PL_VIEWOFS(NULL);
-       const vector pl_viewofs_crouch = PHYS_PL_CROUCH_VIEWOFS(NULL);
-       const entity e = csqcplayer;
+       vector v0 = ((intermission && !autocvar_cl_movement_intermissionrunning) ? '0 0 0' : pmove_vel); // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
+       float vh = PHYS_VIEWHEIGHT(NULL);
+       vector pl_viewofs = PHYS_PL_VIEWOFS(NULL);
+       vector pl_viewofs_crouch = PHYS_PL_CROUCH_VIEWOFS(NULL);
+       entity e = csqcplayer;
        if (e)
        {
                if (servercommandframe == 0 || clientcommandframe == 0)
@@ -239,13 +239,13 @@ void CSQCPlayer_SetCamera()
                }
                else
                {
-                       const int flg = e.iflags; e.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
+                       int flg = e.iflags; e.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
                        InterpolateOrigin_Do(e);
                        e.iflags = flg;
 
                        if (csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
                        {
-                               const vector o = e.origin;
+                               vector o = e.origin;
                                csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
                                CSQCPlayer_PredictTo(e, servercommandframe + 1, false);
                                CSQCPlayer_SetPredictionError(e.origin - o, e.velocity - v0, pmove_onground - IS_ONGROUND(e));
index 3a6765e9d46fbfc7ed9025238cfe8017a1fc5bde..7f8b0cdc837258a7bb5547d984afbcd960430baa 100644 (file)
@@ -28,6 +28,8 @@ void WarpZone_Fade_PreDraw(entity this)
 void WarpZone_Touch(entity this, entity toucher);
 NET_HANDLE(ENT_CLIENT_WARPZONE, bool isnew)
 {
+       if(!warpzone_warpzones_exist)
+               cvar_settemp("r_water", "1"); // HACK for DarkPlaces: always enable reflections when a map has warpzones
        warpzone_warpzones_exist = 1;
        if (!this.enemy)
        {
@@ -84,6 +86,8 @@ NET_HANDLE(ENT_CLIENT_WARPZONE, bool isnew)
 
 NET_HANDLE(ENT_CLIENT_WARPZONE_CAMERA, bool isnew)
 {
+       if(!warpzone_cameras_exist)
+               cvar_settemp("r_water", "1"); // HACK for DarkPlaces: always enable reflections when a map has cameras
        warpzone_cameras_exist = 1;
        this.classname = "func_warpzone_camera";
 
index bd45230c97ab61964f43a7153d69242fb896e38c..a06ac8e23100f7ffbc98312edba482164aec1c77 100644 (file)
@@ -49,6 +49,7 @@ void GameCommand(string theCommand)
                LOG_INFO(_("  sync - reloads all cvars on the current menu page"));
                LOG_INFO(_("  directmenu ITEM - select a menu item as main item"));
                LOG_INFO(_("  dumptree - dump the state of the menu as a tree to the console"));
+               LOG_INFO("\n");
 
                LOG_INFO("Generic commands shared by all programs:");
                GenericCommand_macro_help();
index a5c7cfa8544c70219099a4a12079b7a87dd5a408..6d725259d281ffeb8297902f8c4c62712e54b62f 100644 (file)
@@ -3,26 +3,26 @@
 #include "item/container.qh"
 #include "item/borderimage.qh"
 
-       METHOD(Item, destroy, void(Item this))
+       METHOD(MenuItem, destroy, void(MenuItem this))
        {
                // free memory associated with this
        }
 
-       METHOD(Item, relinquishFocus, void(Item this))
+       METHOD(MenuItem, relinquishFocus, void(MenuItem this))
        {
                entity par = this.parent;
                if (!par) return;
                if (par.instanceOfContainer) par.setFocus(par, NULL);
        }
 
-       METHOD(Item, resizeNotify, void(Item this, vector relOrigin, vector relSize, vector absOrigin, vector absSize))
+       METHOD(MenuItem, resizeNotify, void(MenuItem this, vector relOrigin, vector relSize, vector absOrigin, vector absSize))
        {
                this.origin = absOrigin;
                this.size = absSize;
        }
 
        int autocvar_menu_showboxes;
-       METHOD(Item, draw, void(Item this))
+       METHOD(MenuItem, draw, void(MenuItem this))
        {
                if (!autocvar_menu_showboxes) return;
                vector rgb = '1 0 1';
                }
        }
 
-       METHOD(Item, showNotify, void(Item this))
+       METHOD(MenuItem, showNotify, void(MenuItem this))
        {}
 
-       METHOD(Item, hideNotify, void(Item this))
+       METHOD(MenuItem, hideNotify, void(MenuItem this))
        {}
 
-       METHOD(Item, keyDown, float(Item this, float scan, float ascii, float shift))
+       METHOD(MenuItem, keyDown, float(MenuItem this, float scan, float ascii, float shift))
        {
                return 0;  // unhandled
        }
 
-       METHOD(Item, keyUp, float(Item this, float scan, float ascii, float shift))
+       METHOD(MenuItem, keyUp, float(MenuItem this, float scan, float ascii, float shift))
        {
                return 0;  // unhandled
        }
 
-       METHOD(Item, mouseMove, float(Item this, vector pos))
+       METHOD(MenuItem, mouseMove, float(MenuItem this, vector pos))
        {
                return 0;  // unhandled
        }
 
-       METHOD(Item, mousePress, bool(Item this, vector pos))
+       METHOD(MenuItem, mousePress, bool(MenuItem this, vector pos))
        {
                return false;  // unhandled
        }
 
-       METHOD(Item, mouseDrag, float(Item this, vector pos))
+       METHOD(MenuItem, mouseDrag, float(MenuItem this, vector pos))
        {
                return 0;  // unhandled
        }
 
-       METHOD(Item, mouseRelease, float(Item this, vector pos))
+       METHOD(MenuItem, mouseRelease, float(MenuItem this, vector pos))
        {
                return 0;  // unhandled
        }
 
     void m_play_focus_sound();
 
-       METHOD(Item, focusEnter, void(Item this))
+       METHOD(MenuItem, focusEnter, void(MenuItem this))
        {
                if (this.allowFocusSound) m_play_focus_sound();
        }
 
-       METHOD(Item, focusLeave, void(Item this))
+       METHOD(MenuItem, focusLeave, void(MenuItem this))
        {}
 
-       METHOD(Item, toString, string(Item this))
+       METHOD(MenuItem, toString, string(MenuItem this))
        {
                return string_null;
        }
index 6cee17b3fdd5f9ccd24a66d078f040aaee9110a2..dd1d0679bed6c106e0ff8cad1fd4c9a1064c07e7 100644 (file)
@@ -5,28 +5,28 @@
 #include "draw.qh"
 #include "menu.qh"
 
-CLASS(Item, Object)
-       METHOD(Item, draw, void(Item));
-       METHOD(Item, keyDown, float(Item, float, float, float));
-       METHOD(Item, keyUp, float(Item, float, float, float));
-       METHOD(Item, mouseMove, float(Item, vector));
-       METHOD(Item, mousePress, bool(Item this, vector pos));
-       METHOD(Item, mouseDrag, float(Item, vector));
-       METHOD(Item, mouseRelease, float(Item, vector));
-       METHOD(Item, focusEnter, void(Item));
-       METHOD(Item, focusLeave, void(Item));
-       METHOD(Item, resizeNotify, void(Item, vector, vector, vector, vector));
-       METHOD(Item, relinquishFocus, void(Item));
-       METHOD(Item, showNotify, void(Item));
-       METHOD(Item, hideNotify, void(Item));
-       METHOD(Item, toString, string(Item));
-       METHOD(Item, destroy, void(Item));
-       ATTRIB(Item, focused, float, 0);
-       ATTRIB(Item, focusable, float, 0);
-       ATTRIB(Item, allowFocusSound, float, 0);
-       ATTRIB(Item, parent, entity);
-       ATTRIB(Item, preferredFocusPriority, float, 0);
-       ATTRIB(Item, origin, vector, '0 0 0');
-       ATTRIB(Item, size, vector, '0 0 0');
-       ATTRIB(Item, tooltip, string);
-ENDCLASS(Item)
+CLASS(MenuItem, Object)
+       METHOD(MenuItem, draw, void(MenuItem));
+       METHOD(MenuItem, keyDown, float(MenuItem, float, float, float));
+       METHOD(MenuItem, keyUp, float(MenuItem, float, float, float));
+       METHOD(MenuItem, mouseMove, float(MenuItem, vector));
+       METHOD(MenuItem, mousePress, bool(MenuItem this, vector pos));
+       METHOD(MenuItem, mouseDrag, float(MenuItem, vector));
+       METHOD(MenuItem, mouseRelease, float(MenuItem, vector));
+       METHOD(MenuItem, focusEnter, void(MenuItem));
+       METHOD(MenuItem, focusLeave, void(MenuItem));
+       METHOD(MenuItem, resizeNotify, void(MenuItem, vector, vector, vector, vector));
+       METHOD(MenuItem, relinquishFocus, void(MenuItem));
+       METHOD(MenuItem, showNotify, void(MenuItem));
+       METHOD(MenuItem, hideNotify, void(MenuItem));
+       METHOD(MenuItem, toString, string(MenuItem));
+       METHOD(MenuItem, destroy, void(MenuItem));
+       ATTRIB(MenuItem, focused, float, 0);
+       ATTRIB(MenuItem, focusable, float, 0);
+       ATTRIB(MenuItem, allowFocusSound, float, 0);
+       ATTRIB(MenuItem, parent, entity);
+       ATTRIB(MenuItem, preferredFocusPriority, float, 0);
+       ATTRIB(MenuItem, origin, vector, '0 0 0');
+       ATTRIB(MenuItem, size, vector, '0 0 0');
+       ATTRIB(MenuItem, tooltip, string);
+ENDCLASS(MenuItem)
index b73752685030a8ef57277ddaf3a3b7b7279f26f1..bc2d8e6d0bcce9e991861781e7ff3a76e0f0c760 100644 (file)
@@ -2,7 +2,7 @@
 
 #include <menu/item.qh>
 
-CLASS(Container, Item)
+CLASS(Container, MenuItem)
        METHOD(Container, draw, void(entity));
        METHOD(Container, keyUp, float(entity, float, float, float));
        METHOD(Container, keyDown, float(entity, float, float, float));
index 726f328600d79b570c210b3bb234006868ef2982..0db9c3719ac8ae9135e9662680dfc57ac6686190 100644 (file)
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../item.qh"
-CLASS(Image, Item)
+CLASS(Image, MenuItem)
        METHOD(Image, configureImage, void(entity, string));
        METHOD(Image, draw, void(entity));
        METHOD(Image, toString, string(entity));
index d02f4661f5cd84bf891af26a6c1bef694244f463..3272ed54f39ec1953ff8e61b822cced36287b414 100644 (file)
                        }
 
                // skipping SUPER(InputBox).draw(me);
-               Item_draw(me);
+               MenuItem_draw(me);
        }
 
        void InputBox_showNotify(entity me)
index f91ae8ad3cb12a13265d085fe937cf32959f2a95..2439d1d5beafbfa72c206bfe422a25c9135517e3 100644 (file)
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../item.qh"
-CLASS(Label, Item)
+CLASS(Label, MenuItem)
        METHOD(Label, configureLabel, void(entity, string, float, float));
        METHOD(Label, draw, void(entity));
        METHOD(Label, resizeNotify, void(entity, vector, vector, vector, vector));
index 97f08c98113e520e6d55457356befca52319f28b..c455d2f5ab6c30cd42eee4d01f8dc39edac24f1d 100644 (file)
                }
        }
        AUTOCVAR(menu_scroll_averaging_time, float, 0.16, "smooth scroll averaging time");
-// scroll faster while dragging the scrollbar
+       // scroll faster while dragging the scrollbar
        AUTOCVAR(menu_scroll_averaging_time_pressed, float, 0.06, "smooth scroll averaging time when dragging the scrollbar");
        void ListBox_draw(entity me)
        {
                if (me.scrollPos != me.scrollPosTarget)
                {
                        float averaging_time = (me.pressed == 1)
-                           ? autocvar_menu_scroll_averaging_time_pressed
+                               ? autocvar_menu_scroll_averaging_time_pressed
                                : autocvar_menu_scroll_averaging_time;
                        // this formula works with whatever framerate
                        float f = averaging_time ? exp(-frametime / averaging_time) : 0;
index b3f5164a4d0937f769a55c68fb9cfbf474b07a99..f60066cd7b3d941526040fa85cbea64f6c5247de 100644 (file)
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../item.qh"
-CLASS(ListBox, Item)
+CLASS(ListBox, MenuItem)
        METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector));
        METHOD(ListBox, configureListBox, void(entity, float, float));
        METHOD(ListBox, draw, void(entity));
index 9f953f66f542f9a8b9238d65e813a6b5cdf777c7..a3ad36355d9cece13deb37c750054cfe8794d0d2 100644 (file)
@@ -57,7 +57,7 @@ void XonoticCampaignList_configureXonoticCampaignList(entity me)
        me.configureXonoticListBox(me);
        me.campaignGlob = search_begin("maps/campaign*.txt", true, true);
        me.loadCvars(me);
-       me.campaignGo(me, 0); // takes care of enabling/disabling buttons too
+       me.campaignGo(me, 0); // it makes work buttons too
 }
 
 void XonoticCampaignList_destroy(entity me)
@@ -96,12 +96,9 @@ void XonoticCampaignList_saveCvars(entity me)
 
 void XonoticCampaignList_campaignGo(entity me, float step)
 {
-       float canNext, canPrev;
        string s;
        float i, j, n;
 
-       canNext = canPrev = 0;
-
        if(me.campaignGlob >= 0)
        {
                n = search_getsize(me.campaignGlob);
@@ -136,15 +133,10 @@ void XonoticCampaignList_campaignGo(entity me, float step)
                        s = substring(s, 13, strlen(s) - 17);
                        cvar_set("g_campaign_name", s);
                        me.loadCvars(me);
-                       canNext = (j != n - 1);
-                       canPrev = (j != 0);
+                       me.hasNextCampaign = (j != n - 1);
+                       me.hasPrevCampaign = (j != 0);
                }
        }
-
-       if(me.buttonNext)
-               me.buttonNext.disabled = !canNext;
-       if(me.buttonPrev)
-               me.buttonPrev.disabled = !canPrev;
 }
 
 void MultiCampaign_Next(entity btn, entity me)
@@ -158,6 +150,11 @@ void MultiCampaign_Prev(entity btn, entity me)
 
 void XonoticCampaignList_draw(entity me)
 {
+       if(me.buttonNext)
+               me.buttonNext.disabled = !me.hasNextCampaign;
+       if(me.buttonPrev)
+               me.buttonPrev.disabled = !me.hasPrevCampaign;
+
        if(cvar(me.cvarName) != me.campaignIndex || cvar_string("g_campaign_name") != campaign_name)
                me.loadCvars(me);
        SUPER(XonoticCampaignList).draw(me);
@@ -168,8 +165,10 @@ void XonoticCampaignList_resizeNotify(entity me, vector relOrigin, vector relSiz
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticCampaignList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin1 = 0.5 * me.realFontSize.y;
        me.realUpperMargin2 = me.realUpperMargin1 + 2 * me.realFontSize.y;
 
index 784926f7df8636cd58d8513a24e5aed8b0dc475c..298e27587466697572311d048684b11a2dc21db9 100644 (file)
@@ -34,6 +34,8 @@ CLASS(XonoticCampaignList, XonoticListBox)
        ATTRIB(XonoticCampaignList, cvarName, string);
        METHOD(XonoticCampaignList, loadCvars, void(entity));
        METHOD(XonoticCampaignList, saveCvars, void(entity));
+       ATTRIB(XonoticCampaignList, hasNextCampaign, bool, false);
+       ATTRIB(XonoticCampaignList, hasPrevCampaign, bool, false);
 
        ATTRIB(XonoticCampaignList, buttonNext, entity);
        ATTRIB(XonoticCampaignList, buttonPrev, entity);
index 7bf363def7f4b42ebf413bcea99b7871a7418a39..7499f4462dab79698e1609d071becad5f80f139f 100644 (file)
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../item.qh"
-CLASS(XonoticCrosshairPreview, Item)
+CLASS(XonoticCrosshairPreview, MenuItem)
        METHOD(XonoticCrosshairPreview, configureXonoticCrosshairPreview, void(entity));
        METHOD(XonoticCrosshairPreview, draw, void(entity));
        ATTRIB(XonoticCrosshairPreview, src, string);
index 16d5370f5d7beeef0487001e7aae4c7ad76bb77c..0addd6cbedda7d2bc2f7402b3f9abff336cbfde6 100644 (file)
@@ -92,8 +92,10 @@ void XonoticDemoList_resizeNotify(entity me, vector relOrigin, vector relSize, v
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticDemoList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
        me.columnNameOrigin = me.realFontSize.x;
index 2a2144598bbfecd56097c0a8efea20b4918228fd..ba09c311e4bf0e3fbf99a69898bf91b7fb70e638 100644 (file)
@@ -52,23 +52,8 @@ void GameType_ConfigureSliders(entity me, string pLabel, float pMin, float pMax,
 
 void GameType_ConfigureSliders_for_CurrentGametype(entity me)
 {
-       switch(MapInfo_CurrentGametype())
-       {
-               case MAPINFO_TYPE_CA:              GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_ca_teams_override",          _("The amount of frags needed before the match will end")); break;
-               case MAPINFO_TYPE_FREEZETAG:       GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_freezetag_teams_override",   _("The amount of frags needed before the match will end")); break;
-               case MAPINFO_TYPE_CTF:             GameType_ConfigureSliders(me, _("Capture limit:"),   1,   20,  1, "capturelimit_override",     string_null,                    _("The amount of captures needed before the match will end")); break;
-               case MAPINFO_TYPE_DOMINATION:      GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, "g_domination_point_limit",  "g_domination_teams_override",  _("The amount of points needed before the match will end")); break;
-               case MAPINFO_TYPE_KEYHUNT:         GameType_ConfigureSliders(me, _("Point limit:"),   200, 1500, 50, "g_keyhunt_point_limit",     "g_keyhunt_teams_override",     _("The amount of points needed before the match will end")); break;
-               case MAPINFO_TYPE_LMS:             GameType_ConfigureSliders(me, _("Lives:"),           3,   50,  1, "g_lms_lives_override",      string_null,                    string_null); break;
-               case MAPINFO_TYPE_RACE:            GameType_ConfigureSliders(me, _("Laps:"),            1,   25,  1, "g_race_laps_limit",         string_null,                    string_null); break;
-               case MAPINFO_TYPE_NEXBALL:         GameType_ConfigureSliders(me, _("Goals:"),           1,   50,  1, "g_nexball_goallimit",       string_null,                    _("The amount of goals needed before the match will end")); break;
-               case MAPINFO_TYPE_ASSAULT:         GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_ONSLAUGHT:       GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_CTS:             GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_INVASION:        GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_TEAM_DEATHMATCH: GameType_ConfigureSliders(me, _("Point limit:"),     5,  100,  5, "g_tdm_point_limit",         "g_tdm_teams_override",         _("The amount of points needed before the match will end")); break;
-               default:                           GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        string_null,                    _("The amount of frags needed before the match will end")); break;
-       }
+       Gametype gt = MapInfo_CurrentGametype();
+       gt.m_configuremenu(gt, me, GameType_ConfigureSliders);
 }
 
 entity makeXonoticServerCreateTab()
index 21e7ecadc454fdc7168a1e0c5314892f5d028749..8b6dd4b731512cec3da19fe489dc512a04245108 100644 (file)
@@ -37,10 +37,11 @@ string WeaponArenaString()
        s = "";
        for(int j = 0; j < n; ++j)
        {
-               FOREACH(Weapons, it != WEP_Null, {
-                       if(argv(j) == it.netname)
-                               s = cons_mid(s, " & ", it.m_name);
-               });
+               Weapon wep = Weapons_fromstr(argv(j));
+               if(wep != WEP_Null)
+               {
+                       s = cons_mid(s, " & ", wep.m_name);
+               }
        }
        s = sprintf(_("%s Arena"), s);
 
index 31cc98215d6536745dcea7f5aa153db5b95e53a9..069b22b02d2b3898d691185e597127f77b2c8944 100644 (file)
@@ -80,7 +80,7 @@ void XonoticGameMessageSettingsTab_fill(entity me)
                me.TD(me, 1, 3, e = makeXonoticCheckBoxEx_T(2, 1, "notification_allow_chatboxprint", _("Display all info messages in the chatbox"), "-"));
        me.TR(me);
                me.TD(me, 1, 3, e = makeXonoticCheckBoxEx_T(2, 1, "notification_INFO_QUIT_DISCONNECT", _("Display player statuses in the chatbox"), "-"));
-                       makeMulti(e, "notification_INFO_QUIT_KICK_IDLING notification_INFO_JOIN_CONNECT_TEAM");
+                       makeMulti(e, "notification_INFO_QUIT_KICK_IDLING notification_INFO_JOIN_CONNECT");
        me.TR(me);
        me.TR(me);
                me.TD(me, 1, 3, e = makeXonoticCheckBox_T(0, "notification_CENTER_POWERUP_INVISIBILITY", _("Powerup notifications"), "-"));
index 594b581fe4408d16351225406971870bcda9c2e2..1c7da72919d68934749d76034cef11cd00ae3b97 100644 (file)
@@ -42,7 +42,7 @@ void XonoticMiscSettingsTab_fill(entity me)
                        e.configureXonoticTextSliderValues(e);
        me.TR(me);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Input packets/s:")));
-               me.TD(me, 1, 2, e = makeXonoticSlider_T(20, 100, 5, "cl_netfps",
+               me.TD(me, 1, 2, e = makeXonoticSlider_T(30, 180, 5, "cl_netfps",
                        _("How many input packets to send to the server each second")));
        me.TR(me);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Server queries/s:")));
index a08e4dbbc5fa13dc4eb51680a1bb551a25780fc0..3703393789eaa1125b6777b170d470612f2bea75 100644 (file)
@@ -101,8 +101,10 @@ void XonoticGametypeList_resizeNotify(entity me, vector relOrigin, vector relSiz
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticGametypeList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
        me.columnIconOrigin = 0;
        me.columnIconSize = me.itemAbsSize.y / me.itemAbsSize.x;
index d97d7131a473b36c0a46e8935b1efba6078c51d9..92dee8d85ef3cea77a92dd4e30596852d925b3a9 100644 (file)
@@ -146,8 +146,10 @@ void XonoticHUDSkinList_resizeNotify(entity me, vector relOrigin, vector relSize
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticHUDSkinList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
        me.columnNameOrigin = me.realFontSize.x;
index a14406bcec7202aa518df979bcb29f619f5c90a3..f4417171ff6c6e0b1498bad74f73b5ffd5d4f398 100644 (file)
@@ -103,6 +103,7 @@ void Xonotic_KeyBinds_Read()
        KEYBIND_DEF("+use"                                  , _("drop key / drop flag"));
        KEYBIND_DEF(""                                      , "");
        KEYBIND_DEF(""                                      , _("Misc"));
+       KEYBIND_DEF("kill"                                  , _("respawn"));
        KEYBIND_DEF("quickmenu"                             , _("quick menu"));
        KEYBIND_DEF("menu_showsandboxtools"                 , _("sandbox menu"));
        KEYBIND_DEF("+button8"                              , _("drag object"));
index 73ef8a32ae8cfd738852859b0ccb47fa5bf6bbd0..5184ee1fd546a0f90ad3838462a57c005b7b4a9e 100644 (file)
@@ -88,8 +88,10 @@ void XonoticMapList_resizeNotify(entity me, vector relOrigin, vector relSize, ve
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticMapList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
        me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
 
index c98e9fc9b6fa8f082a3bd346dad51c48edd36048..bbff7d551be6b22d04c68b0f2da9a13ba9af9958 100644 (file)
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../item.qh"
-CLASS(XonoticPicker, Item)
+CLASS(XonoticPicker, MenuItem)
        METHOD(XonoticPicker, configureXonoticPicker, void(entity));
        METHOD(XonoticPicker, mousePress, bool(XonoticPicker this, vector pos));
        METHOD(XonoticPicker, mouseRelease, float(entity, vector));
index c6033050ae6f45d9157b042039788773b67afa72..e90eef23d3a11d2c63a2aa33798f49522508a396 100644 (file)
@@ -61,8 +61,10 @@ void XonoticPlayerList_resizeNotify(entity me, vector relOrigin, vector relSize,
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticPlayerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
        // this list does 1 char left and right margin
index c912ba3a72c49bf40ff49c24ee5eaa199a1efb5f..da5fd486eac5265c53a6ccf2b21b2bb291192b89 100644 (file)
@@ -19,8 +19,10 @@ void XonoticPlayList_resizeNotify(entity me, vector relOrigin, vector relSize, v
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticPlayList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
        me.columnNumberOrigin = 0;
index de0adc793815796c0e175f32bf1e6e473b2709e1..9f5ba787aae88c83af4a14b7e02dd0b2f367f5aa 100644 (file)
@@ -97,8 +97,10 @@ void XonoticScreenshotList_resizeNotify(entity me, vector relOrigin, vector relS
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticScreenshotList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
        me.columnNameOrigin = me.realFontSize.x;
index e72ca12e2409813c024fbca7dc98914a1c56da35..4683c45207a018753df987f7d26f1c98ceda86be 100644 (file)
@@ -111,8 +111,10 @@ void XonoticSkinList_resizeNotify(entity me, vector relOrigin, vector relSize, v
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticSkinList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
        me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
 
index 6d77e1adf929c1e94f2aa964c7edb7bff88837da..99094b0af2cb125550f23bf1bb6137faf8401ed3 100644 (file)
@@ -55,8 +55,10 @@ void XonoticSoundList_resizeNotify(entity me, vector relOrigin, vector relSize,
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticSoundList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
        me.columnNumberOrigin = 0;
index 401a6eadda0026b5f7e2b6533c40352c597961ea..2c600b6f5289e6d92a03569a0c2dabf03cab3ca7 100644 (file)
@@ -270,8 +270,10 @@ void XonoticStatsList_resizeNotify(entity me, vector relOrigin, vector relSize,
        me.itemAbsSize = '0 0 0';
        SUPER(XonoticStatsList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 
-       me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
-       me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+       me.itemAbsSize.y = absSize.y * me.itemHeight;
+       me.itemAbsSize.x = absSize.x * (1 - me.controlWidth);
+       me.realFontSize.y = me.fontSize / me.itemAbsSize.y;
+       me.realFontSize.x = me.fontSize / me.itemAbsSize.x;
        me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 
 #if 0
index fb4bb92a80b5421e80d65ef05d2cd178c04ef6e7..e994491121a8ae1bf012f6981d7c8fdbc1a7a465 100644 (file)
@@ -689,6 +689,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_NEXBALL) \
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
+       /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
 
index a4cb8bd5e1c5779b2274a01448ceeb39a689c867..2ec8386955f032bfa6d44bd4396414aea1c27f5b 100644 (file)
@@ -4,13 +4,13 @@
 #include <server/campaign.qc>
 #include <server/cheats.qc>
 #include <server/client.qc>
+#include <server/clientkill.qc>
 #include <server/g_damage.qc>
 #include <server/g_hook.qc>
 #include <server/g_world.qc>
 #include <server/handicap.qc>
 #include <server/impulse.qc>
 #include <server/ipban.qc>
-#include <server/item_key.qc>
 #include <server/items.qc>
 #include <server/mapvoting.qc>
 #include <server/matrix.qc>
index 0a00d680786fa51c84701339d0b9f82fe01000aa..cc27baf120e93fab2379e7a8643c6e16a1c3c7fe 100644 (file)
@@ -4,13 +4,13 @@
 #include <server/campaign.qh>
 #include <server/cheats.qh>
 #include <server/client.qh>
+#include <server/clientkill.qh>
 #include <server/g_damage.qh>
 #include <server/g_hook.qh>
 #include <server/g_world.qh>
 #include <server/handicap.qh>
 #include <server/impulse.qh>
 #include <server/ipban.qh>
-#include <server/item_key.qh>
 #include <server/items.qh>
 #include <server/mapvoting.qh>
 #include <server/matrix.qh>
index 7d73a73a8b7e65086756c1e261a1b9edcacd3b90..b23f6f148175478fbdc9acb885cc71cb3179b045 100644 (file)
@@ -88,9 +88,6 @@ float autocvar_g_balance_powerup_strength_selfforce;
 //float autocvar_g_balance_powerup_strength_time;
 float autocvar_g_balance_superweapons_time;
 float autocvar_g_balance_selfdamagepercent;
-bool autocvar_g_balance_teams;
-bool autocvar_g_balance_teams_prevent_imbalance;
-//float autocvar_g_balance_teams_scorefactor;
 float autocvar_g_ballistics_density_corpse;
 float autocvar_g_ballistics_density_player;
 float autocvar_g_ballistics_mindistance;
@@ -102,6 +99,7 @@ float autocvar_g_ban_sync_timeout;
 string autocvar_g_ban_sync_trusted_servers;
 bool autocvar_g_ban_sync_trusted_servers_verify;
 string autocvar_g_ban_sync_uri;
+bool autocvar_g_ban_telluser = true;
 string autocvar_g_banned_list;
 bool autocvar_g_banned_list_idmode;
 bool autocvar_g_botclip_collisions;
@@ -109,7 +107,6 @@ bool autocvar_g_campaign;
 #define autocvar_g_campaign_forceteam cvar("g_campaign_forceteam")
 int autocvar_g_campaign_skill;
 int autocvar_g_casings;
-bool autocvar_g_changeteam_banned;
 float autocvar_g_chat_flood_burst;
 float autocvar_g_chat_flood_burst_team;
 float autocvar_g_chat_flood_burst_tell;
@@ -124,11 +121,7 @@ int autocvar_g_chat_nospectators;
 bool autocvar_g_chat_teamcolors;
 bool autocvar_g_chat_tellprivacy;
 bool autocvar_g_forced_respawn;
-string autocvar_g_forced_team_blue;
-string autocvar_g_forced_team_otherwise;
-string autocvar_g_forced_team_pink;
-string autocvar_g_forced_team_red;
-string autocvar_g_forced_team_yellow;
+string autocvar_g_forced_team_otherwise; // TODO: Move to teamplay.qc
 #define autocvar_g_friendlyfire cvar("g_friendlyfire")
 #define autocvar_g_friendlyfire_virtual cvar("g_friendlyfire_virtual")
 #define autocvar_g_friendlyfire_virtual_force cvar("g_friendlyfire_virtual_force")
@@ -321,7 +314,6 @@ float autocvar_sv_maxairspeed;
 float autocvar_sv_maxspeed;
 string autocvar_sv_motd;
 bool autocvar_sv_precacheplayermodels;
-//float autocvar_sv_precacheweapons; // WEAPONTODO?
 bool autocvar_sv_q3acompat_machineshotgunswap;
 bool autocvar_sv_servermodelsonly;
 int autocvar_sv_spectate;
@@ -366,8 +358,6 @@ bool autocvar_sv_vote_gamestart;
 string autocvar_sv_weaponstats_file;
 float autocvar_sv_gibhealth;
 float autocvar_sys_ticrate;
-bool autocvar_teamplay_lockonrestart;
-int autocvar_teamplay_mode;
 #define autocvar_timelimit cvar("timelimit")
 #define autocvar_timelimit_override cvar("timelimit_override")
 float autocvar_timelimit_increment;
@@ -528,4 +518,6 @@ float autocvar_sv_airstopaccelerate;
 float autocvar_sv_track_canjump;
 bool autocvar_sv_showspectators;
 bool autocvar_g_weaponswitch_debug;
+bool autocvar_g_weaponswitch_debug_alternate;
 bool autocvar_g_allow_checkpoints;
+bool autocvar_sv_vq3compat_changehitbox = false;
index 0ecd7b87725b41f7ca894570f7343836a845d3da..0cddd3b27a01df165978105a753016033b7b5c1c 100644 (file)
@@ -90,7 +90,7 @@ float havocbot_symmetry_origin_order;
 .entity bot_basewaypoint;
 .bool navigation_dynamicgoal;
 void navigation_dynamicgoal_init(entity this, bool initially_static);
-void navigation_dynamicgoal_set(entity this);
+void navigation_dynamicgoal_set(entity this, entity dropper);
 void navigation_dynamicgoal_unset(entity this);
 entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
 void navigation_goalrating_end(entity this);
index 3a9befde403f9ee2a8fb6c2f99076af8ca433d9f..768aa6daee2bb2719c759eb5f9f7dc4354f5f264 100644 (file)
@@ -154,11 +154,6 @@ bool bot_shouldattack(entity this, entity targ)
 
 void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
 {
-       if(this.flags & FL_INWATER)
-       {
-               this.bot_aimtarg = NULL;
-               return;
-       }
        this.bot_aimtarg = e1;
        this.bot_aimlatency = CS(this).ping; // FIXME?  Shouldn't this be in the lag item?
        //this.bot_aimorigin = v1;
@@ -180,6 +175,8 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
        float dist, delta_t, blend;
        vector desiredang, diffang;
 
+       this.bot_aimdir_executed = true;
+
        //dprint("aim ", this.netname, ": old:", vtos(this.v_angle));
        // make sure v_angle is sane first
        this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
@@ -245,6 +242,7 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
                + this.bot_4th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_4th
                + this.bot_5th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_5th
        );
+       desiredang.x = bound(-90, desiredang.x, 90);
 
        // calculate turn angles
        diffang = desiredang - this.bot_mouseaim;
index b8b35f1c203a08968e3b82e7029bbfd17109e6dd..1eb71bc7ff58fee1cdaa2eecdf81003495eb5d91 100644 (file)
@@ -59,6 +59,7 @@ vector shotdir;
 .vector lag5_vec3;
 .vector lag5_vec4;
 
+.bool bot_aimdir_executed;
 .float bot_badaimtime;
 .float bot_aimthinktime;
 .float bot_prevaimtime;
index 08d16a71f091dcf626eeae7ae918cb825c8da8e6..0c4668f2f38d3c05eeb5b24e3ff8d44cb861d753 100644 (file)
@@ -123,6 +123,9 @@ void bot_think(entity this)
        // if dead, just wait until we can respawn
        if (IS_DEAD(this))
        {
+               if (bot_waypoint_queue_owner == this)
+                       bot_waypoint_queue_owner = NULL;
+               this.aistatus = 0;
                CS(this).movement = '0 0 0';
                if (this.deadflag == DEAD_DEAD)
                {
@@ -238,7 +241,7 @@ void bot_setnameandstuff(entity this)
 
        this.bot_config_loaded = true;
 
-       // this is really only a default, JoinBestTeam is called later
+       // this is really only a default, TeamBalance_JoinBestTeam is called later
        setcolor(this, stof(bot_shirt) * 16 + stof(bot_pants));
        this.bot_preferredcolors = this.clientcolors;
 
@@ -431,15 +434,15 @@ void bot_clientconnect(entity this)
        else if(this.bot_forced_team==4)
                this.team = NUM_TEAM_4;
        else
-               JoinBestTeam(this, true);
+               TeamBalance_JoinBestTeam(this);
 
        havocbot_setupbot(this);
 }
 
 void bot_removefromlargestteam()
 {
-       CheckAllowedTeams(NULL);
-       GetTeamCounts(NULL);
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       TeamBalance_GetTeamCounts(balance, NULL);
 
        entity best = NULL;
        float besttime = 0;
@@ -458,12 +461,10 @@ void bot_removefromlargestteam()
 
                int thiscount = 0;
 
-               switch(it.team)
+               if (Team_IsValidTeam(it.team))
                {
-                       case NUM_TEAM_1: thiscount = c1; break;
-                       case NUM_TEAM_2: thiscount = c2; break;
-                       case NUM_TEAM_3: thiscount = c3; break;
-                       case NUM_TEAM_4: thiscount = c4; break;
+                       thiscount = TeamBalance_GetNumberOfPlayers(balance,
+                               Team_TeamToIndex(it.team));
                }
 
                if(thiscount > bestcount)
@@ -478,6 +479,7 @@ void bot_removefromlargestteam()
                        best = it;
                }
        });
+       TeamBalance_Destroy(balance);
        if(!bcount)
                return; // no bots to remove
        currentbots = currentbots - 1;
@@ -592,8 +594,6 @@ float bot_fixcount()
        }
 
        int bots;
-       // add/remove bots if needed to make sure there are at least
-       // minplayers+bot_number, or remove all bots if no one is playing
        // But don't remove bots immediately on level change, as the real players
        // usually haven't rejoined yet
        bots_would_leave = false;
@@ -601,15 +601,17 @@ float bot_fixcount()
                bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
        else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
        {
-               float realminplayers, minplayers;
-               realminplayers = autocvar_minplayers;
-               minplayers = max(0, floor(realminplayers));
+               int minplayers = max(0, floor(autocvar_minplayers));
+               int minbots = max(0, floor(autocvar_bot_number));
 
-               float realminbots, minbots;
-               realminbots = autocvar_bot_number;
-               minbots = max(0, floor(realminbots));
+               // add bots to reach minplayers if needed
+               bots = max(minbots, minplayers - activerealplayers);
+               // cap bots to the max players allowed by the server
+               int player_limit = GetPlayerLimit();
+               if(player_limit)
+                       bots = min(bots, player_limit - activerealplayers);
+               bots = min(bots, maxclients - realplayers);
 
-               bots = min(max(minbots, minplayers - activerealplayers), maxclients - realplayers);
                if(bots > minbots)
                        bots_would_leave = true;
        }
index ca567181bb13fd29331a6dd4c10a37d65f2b2c56..c4da14a4beeb29c7474c273879b34abc1c8411de 100644 (file)
@@ -16,7 +16,7 @@ const int AI_STATUS_JETPACK_FLYING             = BIT(9);
 const int AI_STATUS_JETPACK_LANDING            = BIT(10);
 const int AI_STATUS_STUCK                      = BIT(11); // Cannot reach any goal
 
-.float isbot; // true if this client is actually a bot
+.bool isbot; // true if this client is actually a bot
 .int aistatus;
 
 // Skill system
index 357b37da00337b99e929787cafda0741674ce1e3..63f1e0179442941435d9dfc6aeb30ef6e8c9d8c4 100644 (file)
@@ -18,6 +18,7 @@
 #include <common/items/_mod.qh>
 #include <common/wepent.qh>
 
+#include <common/mapobjects/func/ladder.qh>
 #include <common/mapobjects/teleporters.qh>
 #include <common/mapobjects/trigger/jumppads.qh>
 
@@ -104,6 +105,9 @@ void havocbot_ai(entity this)
        }
        havocbot_aim(this);
        lag_update(this);
+
+       this.bot_aimdir_executed = false;
+
        if (this.bot_aimtarg)
        {
                this.aistatus |= AI_STATUS_ATTACKING;
@@ -140,48 +144,17 @@ void havocbot_ai(entity this)
        {
                this.aistatus |= AI_STATUS_ROAMING;
                this.aistatus &= ~AI_STATUS_ATTACKING;
-
-               vector now, next;
-               float aimdistance,skillblend,distanceblend,blend;
-
-               vector v = get_closer_dest(this.goalcurrent, this.origin);
-               if(this.goalcurrent.wpisbox)
-               {
-                       // avoid a glitch when bot is teleported but teleport waypoint isn't removed yet
-                       if(this.goalstack02 && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT
-                       && this.lastteleporttime > 0 && time - this.lastteleporttime < 0.15)
-                               v = (this.goalstack02.absmin + this.goalstack02.absmax) * 0.5;
-                       // aim to teleport origin if bot is inside teleport waypoint but hasn't touched the real teleport yet
-                       else if(boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
-                               v = this.goalcurrent.origin;
-               }
-               next = now = v - (this.origin + this.view_ofs);
-               aimdistance = vlen(now);
-
-               //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
-               if(
-                       this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
-                       !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
-               )
-                       next = ((this.goalstack01.absmin + this.goalstack01.absmax) * 0.5) - (this.origin + this.view_ofs);
-
-               skillblend=bound(0,(skill+this.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
-               distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1);
-               blend = skillblend * (1-distanceblend);
-               //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
-               //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
-               //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
-               v = now + blend * (next - now);
-               //dprint(etos(this), " ");
-               //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
-               //v = now * (distanceblend) + next * (1-distanceblend);
-               if (this.waterlevel < WATERLEVEL_SWIMMING)
-                       v.z = 0;
-               //dprint("walk at:", vtos(v), "\n");
-               //te_lightning2(NULL, this.origin, this.goalcurrent.origin);
-               bot_aimdir(this, v, -1);
        }
+
        havocbot_movetogoal(this);
+       if (!this.bot_aimdir_executed && this.goalcurrent)
+       {
+               // Heading
+               vector dir = get_closer_dest(this.goalcurrent, this.origin);
+               dir -= this.origin + this.view_ofs;
+               dir.z = 0;
+               bot_aimdir(this, dir, -1);
+       }
 
        // if the bot is not attacking, consider reloading weapons
        if (!(this.aistatus & AI_STATUS_ATTACKING))
@@ -488,6 +461,7 @@ void havocbot_movetogoal(entity this)
        vector flatdir;
        vector evadeobstacle;
        vector evadelava;
+       float dodge_enemy_factor = 1;
        float maxspeed;
        //float dist;
        vector dodge;
@@ -567,8 +541,8 @@ void havocbot_movetogoal(entity this)
                if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
                {
                        this.aistatus |= AI_STATUS_OUT_JUMPPAD;
-                       navigation_poptouchedgoals(this);
-                       return;
+                       if(navigation_poptouchedgoals(this))
+                               return;
                }
                else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
                {
@@ -695,7 +669,8 @@ void havocbot_movetogoal(entity this)
 
                        return;
                }
-               else if(GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
+               else if(!this.jumppadcount && !this.goalcurrent.wphardwired
+                       && GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
                {
                        if(this.velocity.z < 0)
                        {
@@ -800,19 +775,28 @@ void havocbot_movetogoal(entity this)
        if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
                locked_goal = true;
 
-       navigation_shortenpath(this);
+       if (navigation_shortenpath(this))
+       {
+               if (vdist(this.origin - this.goalcurrent_prev.origin, <, 50)
+                       && navigation_goalrating_timeout_can_be_anticipated(this))
+                       navigation_goalrating_timeout_force(this);
+       }
 
-       if (IS_MOVABLE(this.goalcurrent))
+       bool goalcurrent_can_be_removed = false;
+       if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
        {
-               if (IS_DEAD(this.goalcurrent))
+               bool freeze_state_changed = (boolean(STAT(FROZEN, this.goalentity)) != this.goalentity_shouldbefrozen);
+               if (IS_DEAD(this.goalcurrent) || (this.goalentity == this.goalcurrent && freeze_state_changed))
                {
+                       goalcurrent_can_be_removed = true;
+                       // don't remove if not visible
                        if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
                        {
                                navigation_goalrating_timeout_force(this);
                                return;
                        }
                }
-               else if (this.bot_tracewalk_time < time)
+               else if (!(STAT(FROZEN, this.goalentity)) && this.bot_tracewalk_time < time)
                {
                        set_tracewalk_dest(this.goalcurrent, this.origin, true);
                        if (!(trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
@@ -824,6 +808,7 @@ void havocbot_movetogoal(entity this)
                        this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
                }
        }
+
        if(!locked_goal)
        {
                // optimize path finding by anticipating goalrating when bot is near a waypoint;
@@ -833,7 +818,7 @@ void havocbot_movetogoal(entity this)
                {
                        if (this.goalcurrent)
                        {
-                               if (IS_MOVABLE(this.goalcurrent) && IS_DEAD(this.goalcurrent))
+                               if (goalcurrent_can_be_removed)
                                {
                                        // remove even if not visible
                                        navigation_goalrating_timeout_force(this);
@@ -873,29 +858,52 @@ void havocbot_movetogoal(entity this)
 
        bool bunnyhop_forbidden = false;
        vector destorg = get_closer_dest(this.goalcurrent, this.origin);
-
-       // in case bot ends up inside the teleport waypoint without touching
-       // the teleport itself, head to the teleport origin
-       if(this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z))
+       if (this.jumppadcount && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
        {
-               bunnyhop_forbidden = true;
+               // if bot used the jumppad, push towards jumppad origin until jumppad waypoint gets removed
                destorg = this.goalcurrent.origin;
-               if(destorg.z > this.origin.z)
-                       PHYS_INPUT_BUTTON_JUMP(this) = true;
+       }
+       else if (this.goalcurrent.wpisbox)
+       {
+               // if bot is inside the teleport waypoint, head to teleport origin until teleport gets used
+               // do it even if bot is on a ledge above a teleport/jumppad so it doesn't get stuck
+               if (boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z)
+                       || (this.absmin.z > destorg.z && destorg.x == this.origin.x && destorg.y == this.origin.y))
+               {
+                       bunnyhop_forbidden = true;
+                       destorg = this.goalcurrent.origin;
+                       if(destorg.z > this.origin.z)
+                               PHYS_INPUT_BUTTON_JUMP(this) = true;
+               }
        }
 
        diff = destorg - this.origin;
 
-       // 1. stop if too close to target player (even if frozen)
-       // 2. stop if the locked goal has been reached
-       if ((IS_PLAYER(this.goalcurrent) && vdist(diff, <, 80))
-               || (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10)))
+       if (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10))
        {
+               // stop if the locked goal has been reached
                destorg = this.origin;
-               diff = '0 0 0';
+               diff = dir = '0 0 0';
        }
-
-       dir = normalize(diff);
+       else if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
+       {
+               if (vdist(diff, <, 80))
+               {
+                       // stop if too close to target player (even if frozen)
+                       destorg = this.origin;
+                       diff = dir = '0 0 0';
+               }
+               else
+               {
+                       // move destorg out of target players, otherwise bot will consider them
+                       // an obstacle that needs to be jumped (especially if frozen)
+                       dir = normalize(diff);
+                       destorg -= dir * PL_MAX_CONST.x * M_SQRT2;
+                       diff = destorg - this.origin;
+               }
+       }
+       else
+               dir = normalize(diff);
        flatdir = (diff.z == 0) ? dir : normalize(vec2(diff));
 
        //if (this.bot_dodgevector_time < time)
@@ -907,9 +915,9 @@ void havocbot_movetogoal(entity this)
 
                this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
                makevectors(this.v_angle.y * '0 1 0');
-               if (this.waterlevel)
+               if (this.waterlevel > WATERLEVEL_WETFEET)
                {
-                       if(this.waterlevel>WATERLEVEL_SWIMMING)
+                       if (this.waterlevel > WATERLEVEL_SWIMMING)
                        {
                                if(!this.goalcurrent)
                                        this.aistatus |= AI_STATUS_OUT_WATER;
@@ -920,7 +928,7 @@ void havocbot_movetogoal(entity this)
                        {
                                dir = flatdir;
                                if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && destorg.z < this.origin.z) &&
-                                       ( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER))
+                                       (this.aistatus & AI_STATUS_OUT_WATER))
                                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                                else
                                        PHYS_INPUT_BUTTON_JUMP(this) = false;
@@ -936,16 +944,20 @@ void havocbot_movetogoal(entity this)
                        // jump if going toward an obstacle that doesn't look like stairs we
                        // can walk up directly
                        vector deviation = '0 0 0';
-                       if (this.velocity)
+                       float current_speed = vlen(vec2(this.velocity));
+                       if (current_speed < maxspeed * 0.2)
+                               current_speed = maxspeed * 0.2;
+                       else
                        {
                                deviation = vectoangles(diff) - vectoangles(this.velocity);
                                while (deviation.y < -180) deviation.y += 360;
                                while (deviation.y > 180) deviation.y -= 360;
                        }
+                       float turning = false;
                        vector flat_diff = vec2(diff);
-                       offset = max(32, vlen(vec2(this.velocity)) * cos(deviation.y * DEG2RAD) * 0.2) * flatdir;
+                       offset = max(32, current_speed * cos(deviation.y * DEG2RAD) * 0.3) * flatdir;
                        vector actual_destorg = this.origin + offset;
-                       if (!this.goalstack01 || this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+                       if (!this.goalstack01 || this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER))
                        {
                                if (vlen2(flat_diff) < vlen2(offset))
                                {
@@ -962,24 +974,53 @@ void havocbot_movetogoal(entity this)
                        {
                                vector next_goal_org = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
                                vector next_dir = normalize(vec2(next_goal_org - destorg));
-                               float next_dist = vlen(vec2(this.origin + offset - destorg));
-                               actual_destorg = vec2(destorg) + next_dist * next_dir;
+                               float dist = vlen(vec2(this.origin + offset - destorg));
+                               // if current and next goal are close to each other make sure
+                               // actual_destorg isn't set beyond next_goal_org
+                               if (dist ** 2 > vlen2(vec2(next_goal_org - destorg)))
+                                       actual_destorg = next_goal_org;
+                               else
+                                       actual_destorg = vec2(destorg) + dist * next_dir;
                                actual_destorg.z = this.origin.z;
+                               turning = true;
                        }
 
-                       tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
-                       if (trace_fraction < 1)
-                       if (trace_plane_normal.z < 0.7)
+                       LABEL(jump_check);
+                       dir = flatdir = normalize(actual_destorg - this.origin);
+
+                       if (turning || fabs(deviation.y) < 50) // don't even try to jump if deviation is too high
                        {
-                               s = trace_fraction;
-                               tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
-                               if (trace_fraction < s + 0.01)
-                               if (trace_plane_normal.z < 0.7)
+                               tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
+                               if (trace_fraction < 1 && trace_plane_normal.z < 0.7)
                                {
                                        s = trace_fraction;
-                                       tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, actual_destorg + jumpstepheightvec, false, this);
-                                       if (trace_fraction > s)
-                                               PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                       tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
+                                       if (trace_fraction < s + 0.01 && trace_plane_normal.z < 0.7)
+                                       {
+                                               // found an obstacle
+                                               if (turning && fabs(deviation.y) > 5)
+                                               {
+                                                       // check if the obstacle is still there without turning
+                                                       actual_destorg = destorg;
+                                                       turning = false;
+                                                       this.bot_tracewalk_time = time + 0.25;
+                                                       goto jump_check;
+                                               }
+                                               s = trace_fraction;
+                                               // don't artificially reduce max jump height in real-time
+                                               // (jumpstepheightvec is reduced a bit to make the jumps easy in tracewalk)
+                                               vector jump_height = (IS_ONGROUND(this)) ? stepheightvec + jumpheight_vec : jumpstepheightvec;
+                                               tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+                                               if (trace_fraction > s)
+                                                       PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                               else
+                                               {
+                                                       jump_height = stepheightvec + jumpheight_vec / 2;
+                                                       tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+                                                       if (trace_fraction > s)
+                                                               PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                               }
+                                       }
                                }
                        }
 
@@ -1066,6 +1107,18 @@ void havocbot_movetogoal(entity this)
                                if(IS_PLAYER(this.goalcurrent))
                                        unreachable = true;
                        }
+
+                       // slow down if bot is in the air and goal is under it
+                       if (!this.goalcurrent.wphardwired
+                               && vdist(flat_diff, <, 250) && this.origin.z - destorg.z > 120
+                               && (!IS_ONGROUND(this) || vdist(vec2(this.velocity), >, maxspeed * 0.3)))
+                       {
+                               // tracebox wouldn't work when bot is still on the ledge
+                               traceline(this.origin, this.origin - '0 0 200', true, this);
+                               if (this.origin.z - trace_endpos.z > 120)
+                                       evadeobstacle = normalize(this.velocity) * -1;
+                       }
+
                        if(unreachable)
                        {
                                navigation_clearroute(this);
@@ -1076,31 +1129,64 @@ void havocbot_movetogoal(entity this)
                }
 
                dodge = havocbot_dodge(this);
-               dodge = dodge * bound(0,0.5+(skill+this.bot_dodgeskill)*0.1,1);
+               if (dodge)
+                       dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
+               dodge += evadeobstacle + evadelava;
                evadelava = evadelava * bound(1,3-(skill+this.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it
-               traceline(this.origin, ( ( this.enemy.absmin + this.enemy.absmax ) * 0.5 ), true, NULL);
-               if(IS_PLAYER(trace_ent))
-                       dir = dir * bound(0,(skill+this.bot_dodgeskill)/7,1);
-
-               dir = normalize(dir + dodge + evadeobstacle + evadelava);
+               if (this.enemy)
+               {
+                       traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
+                       if (IS_PLAYER(trace_ent))
+                               dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
+               }
        //      this.bot_dodgevector = dir;
        //      this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
        }
 
+       float ladder_zdir = 0;
        if(time < this.ladder_time)
        {
                if(this.goalcurrent.origin.z + this.goalcurrent.mins.z > this.origin.z + this.mins.z)
                {
                        if(this.origin.z + this.mins.z  < this.ladder_entity.origin.z + this.ladder_entity.maxs.z)
-                               dir.z = 1;
+                               ladder_zdir = 1;
                }
                else
                {
                        if(this.origin.z + this.mins.z  > this.ladder_entity.origin.z + this.ladder_entity.mins.z)
-                               dir.z = -1;
+                               ladder_zdir = -1;
+               }
+               if (ladder_zdir)
+               {
+                       if (vdist(flatdir, <, 15))
+                               dir = ladder_zdir * '0 0 1';
+                       else
+                       {
+                               dir.z = ladder_zdir * 1.3;
+                               dir = normalize(dir);
+                       }
                }
        }
 
+       if (this.goalcurrent.wpisbox
+               && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
+       {
+               // bot is inside teleport waypoint but hasn't touched the real teleport yet
+               // head to teleport origin
+               dir = (this.goalcurrent.origin - this.origin);
+               dir.z = 0;
+               dir = normalize(dir);
+       }
+
+       if (!this.bot_aimdir_executed)
+               bot_aimdir(this, dir, -1);
+
+       if (!ladder_zdir)
+       {
+               dir *= dodge_enemy_factor;
+               dir = normalize(dir + dodge);
+       }
+
        //dir = this.bot_dodgevector;
        //if (this.bot_dodgevector_jumpbutton)
        //      PHYS_INPUT_BUTTON_JUMP(this) = true;
@@ -1316,7 +1402,7 @@ void havocbot_chooseweapon(entity this, .entity weaponentity)
        // Should it do a weapon combo?
        float af, ct, combo_time, combo;
 
-       af = ATTACK_FINISHED(this, 0);
+       af = ATTACK_FINISHED(this, weaponentity);
        ct = autocvar_bot_ai_weapon_combo_threshold;
 
        // Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
@@ -1454,15 +1540,19 @@ float havocbot_moveto(entity this, vector pos)
                if(autocvar_bot_debug_goalstack)
                        debuggoalstack(this);
 
-               // Heading
-               vector dir = get_closer_dest(this.goalcurrent, this.origin);
-               dir = dir - (this.origin + this.view_ofs);
-               dir.z = 0;
-               bot_aimdir(this, dir, -1);
 
                // Go!
                havocbot_movetogoal(this);
 
+               if (!this.bot_aimdir_executed && this.goalcurrent)
+               {
+                       // Heading
+                       vector dir = get_closer_dest(this.goalcurrent, this.origin);
+                       dir -= this.origin + this.view_ofs;
+                       dir.z = 0;
+                       bot_aimdir(this, dir, -1);
+               }
+
                if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
                {
                        // Step 5: Waypoint reached
@@ -1536,6 +1626,7 @@ void havocbot_setupbot(entity this)
        this.cmd_moveto = havocbot_moveto;
        this.cmd_resetgoal = havocbot_resetgoal;
 
+       // NOTE: bot is not player yet
        havocbot_chooserole(this);
 }
 
index 2f987f674ec8719503137e05452abe5e7701fd1e..b3c0c3ea79191abab45599fe91bc4435631a4866 100644 (file)
@@ -61,5 +61,3 @@ void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrat
  */
 
 .entity draggedby;
-.float ladder_time;
-.entity ladder_entity;
index 9ca2b1208e91a2e828b13ba4ce1646111ac1fc22..1d66df09668a783b4b763a72174e944d2e8e4dcc 100644 (file)
@@ -73,7 +73,7 @@ bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, en
        float enemy_distance = FLOAT_MAX;
        float dist;
 
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && !(IS_DEAD(it) || STAT(FROZEN, it)),
        {
                if (it.team == this.team)
                {
@@ -113,7 +113,7 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
 {
        float rating;
        vector o;
-       ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
+       ratingscale = ratingscale * 0.0001;
 
        IL_EACH(g_items, it.bot_pickup,
        {
@@ -121,8 +121,6 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
                // NOTE: this code assumes each bot rates items in a different frame
                if(it.bot_ratingscale_time == time && ratingscale < it.bot_ratingscale)
                        continue;
-               it.bot_ratingscale_time = time;
-               it.bot_ratingscale = ratingscale;
 
                if(!it.solid)
                {
@@ -175,6 +173,8 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
                if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
                        continue;
 
+               it.bot_ratingscale_time = time;
+               it.bot_ratingscale = ratingscale;
                rating = it.bot_pickupevalfunc(this, it);
                if(rating > 0)
                        navigation_routerating(this, it, rating * ratingscale, 2000);
@@ -191,7 +191,7 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
        if(this.waterlevel>WATERLEVEL_WETFEET)
                return;
 
-       ratingscale = ratingscale * 0.00005; // enemies are rated around 20000 already
+       ratingscale = ratingscale * 0.0001;
 
        float t;
        FOREACH_CLIENT(IS_PLAYER(it) && bot_shouldattack(this, it), {
@@ -235,7 +235,7 @@ void havocbot_role_generic(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
index 555cfd0ef0a1f4da4beb81ebc73577ced6dd0360..250b7cb09c639e45680e699514958d769b97a686 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <common/constants.qh>
 #include <common/net_linked.qh>
+#include <common/mapobjects/func/ladder.qh>
 #include <common/mapobjects/trigger/jumppads.qh>
 
 .float speed;
@@ -49,12 +50,15 @@ bool navigation_goalrating_timeout(entity this)
 #define MAX_CHASE_DISTANCE 700
 bool navigation_goalrating_timeout_can_be_anticipated(entity this)
 {
-       if(time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
+       vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
+       if (vdist(gco - this.origin, >, autocvar_sv_maxspeed * 1.5)
+               && time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
+       {
                return true;
+       }
 
        if (this.goalentity.bot_pickup && time > this.bot_strategytime - 5)
        {
-               vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
                if(!havocbot_goalrating_item_pickable_check_players(this, this.origin, this.goalentity, gco))
                {
                        this.ignoregoal = this.goalentity;
@@ -75,9 +79,11 @@ void navigation_dynamicgoal_init(entity this, bool initially_static)
                this.nearestwaypointtimeout = time;
 }
 
-void navigation_dynamicgoal_set(entity this)
+void navigation_dynamicgoal_set(entity this, entity dropper)
 {
        this.nearestwaypointtimeout = time;
+       if (dropper && dropper.nearestwaypointtimeout && dropper.nearestwaypointtimeout < time + 2)
+               this.nearestwaypoint = dropper.nearestwaypoint;
        if (this.nearestwaypoint)
                this.nearestwaypointtimeout += 2;
 }
@@ -123,8 +129,18 @@ void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest)
                // z coord is set to ent's min height
                tracewalk_dest.x = bound(wm1.x, org.x, wm2.x);
                tracewalk_dest.y = bound(wm1.y, org.y, wm2.y);
-               tracewalk_dest.z = wm1.z;
-               tracewalk_dest_height = wm2.z - wm1.z; // destination height
+               if ((IS_PLAYER(ent) || IS_MONSTER(ent))
+                       && org.x == tracewalk_dest.x && org.y == tracewalk_dest.y && org.z > tracewalk_dest.z)
+               {
+                       tracewalk_dest.z = wm2.z - PL_MIN_CONST.z;
+                       tracewalk_dest_height = 0;
+                       fix_player_dest = false;
+               }
+               else
+               {
+                       tracewalk_dest.z = wm1.z;
+                       tracewalk_dest_height = wm2.z - wm1.z;
+               }
        }
        else
        {
@@ -265,8 +281,7 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float e
        int nav_action;
 
        // Analyze starting point
-       traceline(start, start, MOVE_NORMAL, e);
-       if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+       if (IN_LAVA(start))
                ignorehazards = true;
 
        tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
@@ -732,6 +747,7 @@ void navigation_clearroute(entity this)
        this.goalcurrent_distance_z = FLOAT_MAX;
        this.goalcurrent_distance_time = 0;
        this.goalentity_lock_timeout = 0;
+       this.goalentity_shouldbefrozen = false;
        this.goalentity = NULL;
        this.goalcurrent = NULL;
        this.goalstack01 = NULL;
@@ -900,7 +916,7 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom
        vector pm2 = ent.origin + ent.maxs;
 
        // do two scans, because box test is cheaper
-       IL_EACH(g_waypoints, it != ent && it != except,
+       IL_EACH(g_waypoints, it != ent && it != except && !(it.wpflags & WAYPOINTFLAG_TELEPORT),
        {
                if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
                {
@@ -1202,11 +1218,7 @@ void navigation_markroutes_inverted(entity fixed_source_waypoint)
 // updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
 void navigation_routerating(entity this, entity e, float f, float rangebias)
 {
-       if (!e)
-               return;
-
-       if(e.blacklisted)
-               return;
+       if (!e || e.blacklisted) { return; }
 
        rangebias = waypoint_getlinearcost(rangebias);
        f = waypoint_getlinearcost(f);
@@ -1214,8 +1226,11 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
        if (IS_PLAYER(e))
        {
                bool rate_wps = false;
-               if((e.flags & FL_INWATER) || (e.flags & FL_PARTIALGROUND))
+               if (e.watertype < CONTENT_WATER || (e.waterlevel > WATERLEVEL_WETFEET && !STAT(FROZEN, e))
+                       || (e.flags & FL_PARTIALGROUND))
+               {
                        rate_wps = true;
+               }
 
                if(!IS_ONGROUND(e))
                {
@@ -1388,7 +1403,6 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                nwp = e.nearestwaypoint;
        }
 
-       LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
        if (nwp && nwp.wpcost < 10000000)
        {
                //te_wizspike(nwp.wpnearestpoint);
@@ -1398,12 +1412,12 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                else
                        nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e);
                float cost = nwp.wpcost + nwptoitem_cost;
-               LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = ");
+               LOG_DEBUG("checking ^5", e.classname, "^7 with base rating ^xf04", ftos(f), "^7 and rangebias ^xf40", ftos(rangebias));
                f = f * rangebias / (rangebias + cost);
-               LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
+               LOG_DEBUG("         ^5", e.classname, "^7 with cost ^6", ftos(cost), "^7 and final rating ^2", ftos(f));
                if (navigation_bestrating < f)
                {
-                       LOG_DEBUG("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")");
+                       LOG_DEBUG(" ground path: ^3added goal ^5", e.classname);
                        navigation_bestrating = f;
                        navigation_bestgoal = e;
                }
@@ -1510,12 +1524,12 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
 }
 
 // shorten path by removing intermediate goals
-void navigation_shortenpath(entity this)
+bool navigation_shortenpath(entity this)
 {
        if (!this.goalstack01 || wasfreed(this.goalstack01))
-               return;
+               return false;
        if (this.bot_tracewalk_time > time)
-               return;
+               return false;
        this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
 
        bool cut_allowed = false;
@@ -1554,8 +1568,9 @@ void navigation_shortenpath(entity this)
                                        navigation_poproute(this);
                                }
                                while (this.goalcurrent != next);
+                               return true;
                        }
-                       return;
+                       return false;
                }
        }
 
@@ -1576,8 +1591,10 @@ void navigation_shortenpath(entity this)
                {
                        LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
                        navigation_poproute(this);
+                       return true;
                }
        }
+       return false;
 }
 
 // removes any currently touching waypoints from the goal stack
@@ -1601,6 +1618,16 @@ int navigation_poptouchedgoals(entity this)
                                this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
                                this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
                        }
+                       if(this.jumppadcount)
+                       {
+                               // remove jumppad waypoint after a random delay to prevent bots getting
+                               // stuck on certain jumppads that require an extra initial horizontal speed
+                               float max_delay = 0.1;
+                               if (vdist(vec2(this.velocity), >, 2 * autocvar_sv_maxspeed))
+                                       max_delay = 0.05;
+                               if (time - this.lastteleporttime < random() * max_delay)
+                                       return removed_goals;
+                       }
                        navigation_poproute(this);
                        this.lastteleporttime = 0;
                        ++removed_goals;
@@ -1675,8 +1702,16 @@ int navigation_poptouchedgoals(entity this)
                        gc_min = this.goalcurrent.origin - '1 1 1' * 12;
                        gc_max = this.goalcurrent.origin + '1 1 1' * 12;
                }
-               if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
-                       break;
+               if (time < this.ladder_time)
+               {
+                       if (!boxesoverlap(this.absmin, this.absmax - eZ * STAT(PL_MAX, this).z, gc_min, gc_max))
+                               break;
+               }
+               else
+               {
+                       if (!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
+                               break;
+               }
 
                // Detect personal waypoints
                if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
@@ -1771,6 +1806,7 @@ void navigation_goalrating_end(entity this)
                        this.aistatus |= AI_STATUS_STUCK;
                }
        }
+       this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
 }
 
 void botframe_updatedangerousobjects(float maxupdate)
@@ -1849,6 +1885,17 @@ void navigation_unstuck(entity this)
                                bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
                        }
                }
+
+               // move to a random waypoint while bot is searching for a walkable path;
+               // this is usually sufficient to unstuck bots from bad spots or when other
+               // bots of the same team block all their ways
+               if (!bot_waypoint_queue_bestgoal && (!this.goalentity || random() < 0.1))
+               {
+                       navigation_clearroute(this);
+                       navigation_routetogoal(this, bot_waypoint_queue_goal, this.origin);
+                       navigation_goalrating_timeout_expire(this, 1 + random() * 2);
+               }
+
                bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
 
                if (!bot_waypoint_queue_goal)
@@ -1856,6 +1903,7 @@ void navigation_unstuck(entity this)
                        if (bot_waypoint_queue_bestgoal)
                        {
                                LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
+                               navigation_clearroute(this);
                                navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
                                navigation_goalrating_timeout_set(this);
                                this.aistatus &= ~AI_STATUS_STUCK;
index f3103cc4fcd39d875708a0612e83c59addd03095..b8b067c3b993cae47c20e7b5219a2d36aa913b9f 100644 (file)
@@ -30,6 +30,7 @@ entity navigation_bestgoal;
 .float goalcurrent_distance_time;
 
 .float goalentity_lock_timeout;
+.bool goalentity_shouldbefrozen;
 
 .entity nearestwaypoint;
 .float nearestwaypointtimeout;
@@ -82,7 +83,7 @@ float bot_waypoint_queue_bestgoalrating;
 .entity bot_basewaypoint;
 .bool navigation_dynamicgoal;
 void navigation_dynamicgoal_init(entity this, bool initially_static);
-void navigation_dynamicgoal_set(entity this);
+void navigation_dynamicgoal_set(entity this, entity dropper);
 void navigation_dynamicgoal_unset(entity this);
 
 .int nav_submerged_state;
@@ -114,7 +115,7 @@ void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vecto
 void navigation_markroutes(entity this, entity fixed_source_waypoint);
 void navigation_markroutes_inverted(entity fixed_source_waypoint);
 void navigation_routerating(entity this, entity e, float f, float rangebias);
-void navigation_shortenpath(entity this);
+bool navigation_shortenpath(entity this);
 int navigation_poptouchedgoals(entity this);
 void navigation_goalrating_start(entity this);
 void navigation_goalrating_end(entity this);
index 48975b8367e58766668e59027e5e73000b9b4d51..555f6fc58a54c8e898f4472e7ee81577ea99b579 100644 (file)
@@ -1084,12 +1084,12 @@ float bot_cmd_debug_assert_canfire(entity this)
                        LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by weaponentity state");
                }
        }
-       else if(ATTACK_FINISHED(this, slot) > time)
+       else if(ATTACK_FINISHED(this, weaponentity) > time)
        {
                if(f)
                {
                        this.colormod = '8 0 8';
-                       LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left)");
+                       LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, weaponentity) - time), " seconds left)");
                }
        }
        else if(this.(weaponentity).tuba_note)
@@ -1105,7 +1105,7 @@ float bot_cmd_debug_assert_canfire(entity this)
                if(!f)
                {
                        this.colormod = '8 8 0';
-                       LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left");
+                       LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, weaponentity) - time), " seconds left");
                }
        }
 
index 674ab634a1c768889ff3ac4025ea07008221b813..2ea30b50fe31ab5f742df66155f60c327ce3e073 100644 (file)
@@ -128,7 +128,7 @@ void waypoint_unreachable(entity pl)
        if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
 }
 
-vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
+vector waypoint_getSymmetricalPoint(vector org, int ctf_flags)
 {
        vector new_org = org;
        if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
@@ -251,6 +251,8 @@ void waypoint_spawn_fromeditor(entity pl)
                   || (autocvar_g_waypointeditor_symmetrical < 0));
        if(autocvar_g_waypointeditor_symmetrical_order >= 2)
                ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+       if (sym && ctf_flags < 2)
+               ctf_flags = 2;
        int wp_num = ctf_flags;
 
        if(!PHYS_INPUT_BUTTON_CROUCH(pl))
@@ -260,7 +262,7 @@ void waypoint_spawn_fromeditor(entity pl)
                {
                        vector item_org = (it.absmin + it.absmax) * 0.5;
                        item_org.z = it.absmin.z - PL_MIN_CONST.z;
-                       if(vlen(item_org - org) < 30)
+                       if (vlen(item_org - org) < 20)
                        {
                                org = item_org;
                                break;
@@ -279,7 +281,7 @@ void waypoint_spawn_fromeditor(entity pl)
        bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
        if(sym)
        {
-               org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+               org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
                if (vdist(org - pl.origin, >, 32))
                {
                        if(wp_num > 2)
@@ -293,7 +295,6 @@ void waypoint_spawn_fromeditor(entity pl)
 
 void waypoint_remove(entity wp)
 {
-       // tell all waypoints linked to wp that they need to relink
        IL_EACH(g_waypoints, it != wp,
        {
                if (waypoint_islinked(it, wp))
@@ -311,6 +312,8 @@ void waypoint_remove_fromeditor(entity pl)
                   || (autocvar_g_waypointeditor_symmetrical < 0));
        if(autocvar_g_waypointeditor_symmetrical_order >= 2)
                ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+       if (sym && ctf_flags < 2)
+               ctf_flags = 2;
        int wp_num = ctf_flags;
 
        LABEL(remove_wp);
@@ -326,7 +329,7 @@ void waypoint_remove_fromeditor(entity pl)
        entity wp_sym = NULL;
        if (sym)
        {
-               vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+               vector org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
                FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
                        if(vdist(org - it.origin, <, 3))
                        {
@@ -718,6 +721,7 @@ bool waypoint_load_links()
 
        bool parse_comments = true;
        float ver = 0;
+       string links_time = string_null;
 
        while ((s = fgets(file)))
        {
@@ -727,13 +731,18 @@ bool waypoint_load_links()
                        {
                                if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
                                        ver = stof(substring(s, 19, -1));
+                               else if(substring(s, 2, 14) == "WAYPOINT_TIME ")
+                                       links_time = substring(s, 16, -1);
                                continue;
                        }
                        else
                        {
-                               if(ver < WAYPOINT_VERSION)
+                               if(ver < WAYPOINT_VERSION || links_time != waypoint_time)
                                {
-                                       LOG_TRACE("waypoint links for this map are outdated.");
+                                       if (links_time != waypoint_time)
+                                               LOG_TRACE("waypoint links for this map are not made for these waypoints.");
+                                       else
+                                               LOG_TRACE("waypoint links for this map are outdated.");
                                        if (g_assault)
                                        {
                                                LOG_TRACE("Assault waypoint links need to be manually updated in the editor");
@@ -996,6 +1005,8 @@ void waypoint_save_links()
        }
 
        fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
+       if (waypoint_time != "")
+               fputs(file, strcat("//", "WAYPOINT_TIME ", waypoint_time, "\n"));
 
        int c = 0;
        IL_EACH(g_waypoints, true,
@@ -1005,6 +1016,7 @@ void waypoint_save_links()
                        entity link = waypoint_get_link(it, j);
                        if(link)
                        {
+                               // NOTE: vtos rounds vector components to 1 decimal place
                                string s = strcat(vtos(it.origin), "*", vtos(link.origin), "\n");
                                fputs(file, s);
                                ++c;
@@ -1063,7 +1075,12 @@ void waypoint_saveall()
        // (they are read as a waypoint with origin '0 0 0' and flag 0 though)
        fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
        fputs(file, strcat("//", "WAYPOINT_SYMMETRY ", sym_str, "\n"));
-       fputs(file, strcat("//", "\n"));
+
+       strcpy(waypoint_time, strftime(true, "%Y-%m-%d %H:%M:%S"));
+       fputs(file, strcat("//", "WAYPOINT_TIME ", waypoint_time, "\n"));
+       //fputs(file, strcat("//", "\n"));
+       //fputs(file, strcat("//", "\n"));
+       //fputs(file, strcat("//", "\n"));
 
        int c = 0;
        IL_EACH(g_waypoints, true,
@@ -1072,6 +1089,7 @@ void waypoint_saveall()
                        continue;
 
                string s;
+               // NOTE: vtos rounds vector components to 1 decimal place
                s = strcat(vtos(it.origin + it.mins), "\n");
                s = strcat(s, vtos(it.origin + it.maxs));
                s = strcat(s, "\n");
@@ -1135,6 +1153,8 @@ float waypoint_loadall()
                                        if (tokens > 2) { sym_param2 = stof(argv(2)); }
                                        if (tokens > 3) { sym_param3 = stof(argv(3)); }
                                }
+                               else if(substring(s, 2, 14) == "WAYPOINT_TIME ")
+                                       strcpy(waypoint_time, substring(s, 16, -1));
                                continue;
                        }
                        else
index 595c2d058fcc1cd82fa1a082ffd29096f90adc3b..34372fe9661f535439b4c815cb6ef5c632dac6c8 100644 (file)
@@ -6,7 +6,8 @@
 // increase by 0.01 when changes require only waypoint relinking
 // increase by 1 when changes require to manually edit waypoints
 // max 2 decimal places, always specified
-#define WAYPOINT_VERSION 1.01
+const float WAYPOINT_VERSION = 1.01;
+string waypoint_time;
 
 // fields you can query using prvm_global server to get some statistics about waypoint linking culling
 float relink_total, relink_walkculled, relink_pvsculled, relink_lengthculled;
index 04172b5eb2b59d74de854c3c1d440ad4c5f7827a..5ca5270d614dda0b5212a827c5fa5922162e8f51 100644 (file)
@@ -6,6 +6,7 @@
 #include <server/resources.qh>
 
 #include "g_damage.qh"
+#include "clientkill.qh"
 #include "player.qh"
 #include "race.qh"
 #include "../common/mapobjects/teleporters.qh"
@@ -45,7 +46,7 @@ void Drag_MoveDrag(entity from, entity to) { }
 
 #else
 
-.float maycheat;
+.bool maycheat;
 float gamestart_sv_cheats;
 
 
@@ -59,7 +60,7 @@ void CheatShutdown()
 {
 }
 
-float CheatsAllowed(entity this, float i, float argc, float fr) // the cheat gets passed as argument for possible future ACL checking
+float CheatsAllowed(entity this, float i, int argc, float fr) // the cheat gets passed as argument for possible future ACL checking
 {
        // dead people cannot cheat
        if(IS_DEAD(this))
@@ -838,7 +839,7 @@ float Drag(entity this, float force_allow_pick, float ischeat)
                                                }
                                                // Find e and pick
                                                if(e && pick)
-                                                       if(Drag_IsDraggable(e))
+                                                       if(Drag_IsDraggable(e, this))
                                                        {
                                                                if(ischeat)
                                                                        IS_CHEAT(this, 0, 0, CHRAME_DRAG);
@@ -914,31 +915,24 @@ void Drag_Finish(entity dragger)
        }
 }
 
-float Drag_IsDraggable(entity draggee)
+bool drag_undraggable(entity draggee, entity dragger)
+{
+       // stuff probably shouldn't need this, we should figure out why they do!
+       // exceptions of course are observers and weapon entities, where things mess up
+       return false;
+}
+
+float Drag_IsDraggable(entity draggee, entity dragger)
 {
        // TODO add more checks for bad stuff here
        if(draggee == NULL)
                return false;
-       if(draggee.classname == "func_bobbing")
-               return false;
        if(draggee.classname == "door") // FIXME find out why these must be excluded, or work around the problem (trying to drag these causes like 4 fps)
-               return false;
-       if(draggee.classname == "plat")
-               return false;
-       if(draggee.classname == "func_button")
-               return false;
+               return false; // probably due to BSP collision
 //     if(draggee.model == "")
 //             return false;
-       if(IS_SPEC(draggee))
-               return false;
-       if(IS_OBSERVER(draggee))
-               return false;
-       if(draggee.classname == "exteriorweaponentity")
-               return false;
-       if(draggee.classname == "weaponentity")
-               return false;
 
-       return true;
+       return ((draggee.draggable) ? draggee.draggable(draggee, dragger) : true);
 }
 
 float Drag_MayChangeAngles(entity draggee)
@@ -1016,7 +1010,7 @@ float Drag_IsDragging(entity dragger)
                dragger.dragentity = NULL;
                return false;
        }
-       if(!Drag_CanDrag(dragger) || !Drag_IsDraggable(dragger.dragentity))
+       if(!Drag_CanDrag(dragger) || !Drag_IsDraggable(dragger.dragentity, dragger))
        {
                Drag_Finish(dragger);
                return false;
index 0dc6a92d9c45b5f103e432fabaa59f8c3db45bb1..962e017a19d6dad9e4919c86d53b20bb92efb84c 100644 (file)
@@ -14,12 +14,15 @@ float CheatFrame(entity this);
 
 const float CHRAME_DRAG = 8;
 
+bool drag_undraggable(entity draggee, entity dragger);
+
+.bool(entity this, entity dragger) draggable;
 void Drag_MoveDrag(entity from, entity to); // call this from CopyBody
 void DragBox_Think(entity this);
 float Drag(entity this, float force_allow_pick, float ischeat);
 void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
 void Drag_Finish(entity dragger);
-float Drag_IsDraggable(entity draggee);
+bool Drag_IsDraggable(entity draggee, entity dragger);
 float Drag_MayChangeAngles(entity draggee);
 void Drag_MoveForward(entity dragger);
 void Drag_SetSpeed(entity dragger, float s);
index c3a9178738684318fa03ae73e24b9b372300a989..af05fd79cf023e31ee8e73ce32f5e0eb4feae123 100644 (file)
@@ -17,6 +17,7 @@
 #include "g_hook.qh"
 #include "command/common.qh"
 #include "command/vote.qh"
+#include "clientkill.qh"
 #include "cheats.qh"
 #include "g_world.qh"
 #include "race.qh"
 #include "../common/wepent.qh"
 #include <common/state.qh>
 
+#include "compat/quake3.qh"
+
 #include <common/effects/qc/globalsound.qh>
 
 #include "../common/mapobjects/func/conveyor.qh"
 #include "../common/mapobjects/teleporters.qh"
 #include "../common/mapobjects/target/spawnpoint.qh"
+#include <common/mapobjects/trigger/counter.qh>
 
 #include "../common/vehicles/all.qh"
 
@@ -270,7 +274,7 @@ void PutObserverInServer(entity this)
 
     RemoveGrapplingHooks(this);
        Portal_ClearAll(this);
-       Unfreeze(this);
+       Unfreeze(this, false);
        SetSpectatee(this, NULL);
 
        if (this.alivetime)
@@ -284,27 +288,11 @@ void PutObserverInServer(entity this)
 
        WaypointSprite_PlayerDead(this);
 
-       if (mutator_returnvalue) {
-           // mutator prevents resetting teams+score
-       } else {
-               int oldteam = this.team;
-               this.team = -1;  // move this as it is needed to log the player spectating in eventlog
-               MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
-        this.frags = FRAGS_SPECTATOR;
-        PlayerScore_Clear(this);  // clear scores when needed
-    }
-
        if (CS(this).killcount != FRAGS_SPECTATOR)
        {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname);
                if(!game_stopped)
                if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2))
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
-
-               if(!CS(this).just_joined)
-                       LogTeamchange(this.playerid, -1, 4);
-               else
-                       CS(this).just_joined = false;
        }
 
        accuracy_resend(this);
@@ -347,7 +335,10 @@ void PutObserverInServer(entity this)
        this.strength_finished = 0;
        this.invincible_finished = 0;
        this.superweapons_finished = 0;
-       this.dphitcontentsmask = 0;
+       //this.dphitcontentsmask = 0;
+       this.dphitcontentsmask = DPCONTENTS_SOLID;
+       if (autocvar_g_playerclip_collisions)
+               this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
        this.pushltime = 0;
        this.istypefrag = 0;
        setthink(this, func_null);
@@ -356,6 +347,7 @@ void PutObserverInServer(entity this)
        this.crouch = false;
        STAT(REVIVE_PROGRESS, this) = 0;
        this.revival_time = 0;
+       this.draggable = drag_undraggable;
 
        this.items = 0;
        STAT(WEAPONS, this) = '0 0 0';
@@ -389,6 +381,16 @@ void PutObserverInServer(entity this)
                if(axh.owner == this && axh != NULL && !wasfreed(axh))
                        delete(axh);
        }
+       
+       if (mutator_returnvalue)
+       {
+               // mutator prevents resetting teams+score
+       }
+       else
+       {
+               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+               this.frags = FRAGS_SPECTATOR;
+    }
 }
 
 int player_getspecies(entity this)
@@ -521,7 +523,7 @@ void PutPlayerInServer(entity this)
        accuracy_resend(this);
 
        if (this.team < 0)
-               JoinBestTeam(this, true);
+               TeamBalance_JoinBestTeam(this);
 
        entity spot = SelectSpawnPoint(this, false);
        if (!spot) {
@@ -676,6 +678,8 @@ void PutPlayerInServer(entity this)
        this.event_damage = PlayerDamage;
        this.event_heal = PlayerHeal;
 
+       this.draggable = func_null;
+
        if(!this.bot_attack)
                IL_PUSH(g_bot_targets, this);
        this.bot_attack = true;
@@ -707,6 +711,9 @@ void PutPlayerInServer(entity this)
 
        this.speedrunning = false;
 
+       this.counter_cnt = 0;
+       this.fragsfilter_cnt = 0;
+
        target_voicescript_clear(this);
 
        // reset fields the weapons may use
@@ -723,13 +730,13 @@ void PutPlayerInServer(entity this)
        });
 
        {
-               string s = spot.target;
-               spot.target = string_null;
+               //string s = spot.target;
+               //spot.target = string_null;
                SUB_UseTargets(spot, this, NULL);
-               spot.target = s;
+               //spot.target = s;
        }
 
-       Unfreeze(this);
+       Unfreeze(this, false);
 
        MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
 
@@ -902,204 +909,6 @@ void DecodeLevelParms(entity this)
        MUTATOR_CALLHOOK(DecodeLevelParms);
 }
 
-/*
-=============
-ClientKill
-
-Called when a client types 'kill' in the console
-=============
-*/
-
-.float clientkill_nexttime;
-void ClientKill_Now_TeamChange(entity this)
-{
-       if(this.killindicator_teamchange == -1)
-       {
-               JoinBestTeam( this, true );
-       }
-       else if(this.killindicator_teamchange == -2)
-       {
-               if(blockSpectators)
-                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
-               PutObserverInServer(this);
-       }
-       else
-               SV_ChangeTeam(this, this.killindicator_teamchange - 1);
-       this.killindicator_teamchange = 0;
-}
-
-void ClientKill_Now(entity this)
-{
-       if(this.vehicle)
-       {
-           vehicles_exit(this.vehicle, VHEF_RELEASE);
-           if(!this.killindicator_teamchange)
-           {
-            this.vehicle_health = -1;
-            Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
-           }
-       }
-
-       if(this.killindicator && !wasfreed(this.killindicator))
-               delete(this.killindicator);
-
-       this.killindicator = NULL;
-
-       if(this.killindicator_teamchange)
-               ClientKill_Now_TeamChange(this);
-
-       if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
-       {
-               Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
-       }
-
-       // now I am sure the player IS dead
-}
-void KillIndicator_Think(entity this)
-{
-       if (game_stopped)
-       {
-               this.owner.killindicator = NULL;
-               delete(this);
-               return;
-       }
-
-       if (this.owner.alpha < 0 && !this.owner.vehicle)
-       {
-               this.owner.killindicator = NULL;
-               delete(this);
-               return;
-       }
-
-       if(this.cnt <= 0)
-       {
-               ClientKill_Now(this.owner);
-               return;
-       }
-    else if(this.count == 1) // count == 1 means that it's silent
-    {
-        this.nextthink = time + 1;
-        this.cnt -= 1;
-    }
-       else
-       {
-               if(this.cnt <= 10)
-                       setmodel(this, MDL_NUM(this.cnt));
-               if(IS_REAL_CLIENT(this.owner))
-               {
-                       if(this.cnt <= 10)
-                               { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); }
-               }
-               this.nextthink = time + 1;
-               this.cnt -= 1;
-       }
-}
-
-float clientkilltime;
-void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec
-{
-       float killtime;
-       float starttime;
-
-       if (game_stopped)
-               return;
-
-       killtime = autocvar_g_balance_kill_delay;
-
-    if(MUTATOR_CALLHOOK(ClientKill, this, killtime))
-       return;
-    killtime = M_ARGV(1, float);
-
-       this.killindicator_teamchange = targetteam;
-
-    if(!this.killindicator)
-       {
-               if(!IS_DEAD(this))
-               {
-                       killtime = max(killtime, this.clientkill_nexttime - time);
-                       this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
-               }
-
-               if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this))
-               {
-                       ClientKill_Now(this);
-               }
-               else
-               {
-                       starttime = max(time, clientkilltime);
-
-                       this.killindicator = spawn();
-                       this.killindicator.owner = this;
-                       this.killindicator.scale = 0.5;
-                       setattachment(this.killindicator, this, "");
-                       setorigin(this.killindicator, '0 0 52');
-                       setthink(this.killindicator, KillIndicator_Think);
-                       this.killindicator.nextthink = starttime + (this.lip) * 0.05;
-                       clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05);
-                       this.killindicator.cnt = ceil(killtime);
-                       this.killindicator.count = bound(0, ceil(killtime), 10);
-                       //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n"));
-
-                       IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST),
-                       {
-                               it.killindicator = spawn();
-                               it.killindicator.owner = it;
-                               it.killindicator.scale = 0.5;
-                               setattachment(it.killindicator, it, "");
-                               setorigin(it.killindicator, '0 0 52');
-                               setthink(it.killindicator, KillIndicator_Think);
-                               it.killindicator.nextthink = starttime + (it.lip) * 0.05;
-                               //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05);
-                               it.killindicator.cnt = ceil(killtime);
-                       });
-                       this.lip = 0;
-               }
-       }
-       if(this.killindicator)
-       {
-               if(targetteam == 0) // just die
-               {
-                       this.killindicator.colormod = '0 0 0';
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt);
-               }
-               else if(targetteam == -1) // auto
-               {
-                       this.killindicator.colormod = '0 1 0';
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt);
-               }
-               else if(targetteam == -2) // spectate
-               {
-                       this.killindicator.colormod = '0.5 0.5 0.5';
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt);
-               }
-               else
-               {
-                       this.killindicator.colormod = Team_ColorRGB(targetteam);
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt);
-               }
-       }
-
-}
-
-void ClientKill (entity this)
-{
-       // TODO: once .health is removed, will need to check it here for the "already dead" message!
-
-       if(game_stopped) return;
-       if(this.player_blocked) return;
-       if(STAT(FROZEN, this)) return;
-
-       ClientKill_TeamChange(this, 0);
-}
-
 void FixClientCvars(entity e)
 {
        // send prediction settings to the client
@@ -1175,6 +984,76 @@ void ClientPreConnect(entity this)
 }
 #endif
 
+string GetClientVersionMessage(entity this)
+{
+       if (CS(this).version_mismatch) {
+               if(CS(this).version < autocvar_gameversion) {
+                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
+                               "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
+               } else {
+                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
+                               "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
+               }
+       } else {
+               return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
+       }
+}
+
+string getwelcomemessage(entity this)
+{
+       MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
+       string modifications = M_ARGV(0, string);
+
+       if(g_weaponarena)
+       {
+               if(g_weaponarena_random)
+                       modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
+               else
+                       modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
+       }
+       else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
+               modifications = strcat(modifications, ", No start weapons");
+       if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
+               modifications = strcat(modifications, ", Low gravity");
+       if(g_weapon_stay && !g_cts)
+               modifications = strcat(modifications, ", Weapons stay");
+       if(g_jetpack)
+               modifications = strcat(modifications, ", Jet pack");
+       if(autocvar_g_powerups == 0)
+               modifications = strcat(modifications, ", No powerups");
+       if(autocvar_g_powerups > 0)
+               modifications = strcat(modifications, ", Powerups");
+       modifications = substring(modifications, 2, strlen(modifications) - 2);
+
+       string versionmessage = GetClientVersionMessage(this);
+       string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
+
+       if(modifications != "")
+               s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+
+       if(cache_lastmutatormsg != autocvar_g_mutatormsg)
+       {
+               strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
+               strcpy(cache_mutatormsg, cache_lastmutatormsg);
+       }
+
+       if (cache_mutatormsg != "") {
+               s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
+       }
+
+       string mutator_msg = "";
+       MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
+       mutator_msg = M_ARGV(0, string);
+
+       s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
+
+       string motd = autocvar_sv_motd;
+       if (motd != "") {
+               s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+       }
+       return s;
+}
+
 /**
 =============
 ClientConnect
@@ -1195,43 +1074,11 @@ void ClientConnect(entity this)
        TRANSMUTE(Client, this);
        CS(this).version_nagtime = time + 10 + random() * 10;
 
-       // identify the right forced team
-       if (autocvar_g_campaign)
-       {
-               if (IS_REAL_CLIENT(this)) // only players, not bots
-               {
-                       switch (autocvar_g_campaign_forceteam)
-                       {
-                               case 1: this.team_forced = NUM_TEAM_1; break;
-                               case 2: this.team_forced = NUM_TEAM_2; break;
-                               case 3: this.team_forced = NUM_TEAM_3; break;
-                               case 4: this.team_forced = NUM_TEAM_4; break;
-                               default: this.team_forced = 0;
-                       }
-               }
-       }
-       else if (PlayerInList(this, autocvar_g_forced_team_red))    this.team_forced = NUM_TEAM_1;
-       else if (PlayerInList(this, autocvar_g_forced_team_blue))   this.team_forced = NUM_TEAM_2;
-       else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
-       else if (PlayerInList(this, autocvar_g_forced_team_pink))   this.team_forced = NUM_TEAM_4;
-       else switch (autocvar_g_forced_team_otherwise)
-       {
-               default: this.team_forced = 0; break;
-               case "red": this.team_forced = NUM_TEAM_1; break;
-               case "blue": this.team_forced = NUM_TEAM_2; break;
-               case "yellow": this.team_forced = NUM_TEAM_3; break;
-               case "pink": this.team_forced = NUM_TEAM_4; break;
-               case "spectate":
-               case "spectator":
-                       this.team_forced = -1;
-                       break;
-       }
-       if (!teamplay && this.team_forced > 0) this.team_forced = 0;
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
+
+       bot_clientconnect(this);
 
-       int playerid_save = this.playerid;
-       this.playerid = 0; // silent
-       JoinBestTeam(this, false); // if the team number is valid, keep it
-       this.playerid = playerid_save;
+       Player_DetermineForcedTeam(this);
 
        TRANSMUTE(Observer, this);
 
@@ -1246,15 +1093,8 @@ void ClientConnect(entity this)
        if (autocvar_sv_eventlog)
                GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
 
-       LogTeamchange(this.playerid, this.team, 1);
-
        CS(this).just_joined = true;  // stop spamming the eventlog with additional lines when the client connects
 
-       if(teamplay && IS_PLAYER(this))
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_CONNECT_TEAM), this.netname);
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
-
        stuffcmd(this, clientstuff, "\n");
        stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
 
@@ -1266,12 +1106,9 @@ void ClientConnect(entity this)
        // notify about available teams
        if (teamplay)
        {
-               CheckAllowedTeams(this);
-               int t = 0;
-               if (c1 >= 0) t |= BIT(0);
-               if (c2 >= 0) t |= BIT(1);
-               if (c3 >= 0) t |= BIT(2);
-               if (c4 >= 0) t |= BIT(3);
+               entity balance = TeamBalance_CheckAllowedTeams(this);
+               int t = TeamBalance_GetAllowedTeams(balance);
+               TeamBalance_Destroy(balance);
                stuffcmd(this, sprintf("set _teams_available %d\n", t));
        }
        else
@@ -1309,6 +1146,8 @@ void ClientConnect(entity this)
        if (IS_REAL_CLIENT(this))
                sv_notice_join(this);
 
+       this.move_qcphysics = false;
+
        // update physics stats (players can spawn before physics runs)
        Physics_UpdateStats(this);
 
@@ -1362,7 +1201,7 @@ void ClientDisconnect(entity this)
 
        Portal_ClearAll(this);
 
-       Unfreeze(this);
+       Unfreeze(this, false);
 
        RemoveGrapplingHooks(this);
 
@@ -1402,7 +1241,7 @@ void ChatBubbleThink(entity this)
 
        if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
        {
-               if ( CS(this.owner).active_minigame )
+               if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
                        this.mdl = "models/sprites/minigame_busy.iqm";
                else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
                        this.mdl = "models/misc/chatbubble.spr";
@@ -1622,9 +1461,12 @@ void player_powerups(entity this)
                        if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS))
                        {
                                this.items = this.items | IT_SUPERWEAPON;
-                               if(!g_cts)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
+                               if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
+                               {
+                                       if(!g_cts)
+                                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
+                                       Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
+                               }
                        }
                        else
                        {
@@ -1787,7 +1629,7 @@ void GetPressedKeys(entity this)
        keys = BITSET(keys, KEY_LEFT,           CS(this).movement.y < 0);
 
        keys = BITSET(keys, KEY_JUMP,           PHYS_INPUT_BUTTON_JUMP(this));
-       keys = BITSET(keys, KEY_CROUCH,         PHYS_INPUT_BUTTON_CROUCH(this));
+       keys = BITSET(keys, KEY_CROUCH,         IS_DUCKED(this)); // workaround: player can't un-crouch until their path is clear, so we keep the button held here
        keys = BITSET(keys, KEY_ATCK,           PHYS_INPUT_BUTTON_ATCK(this));
        keys = BITSET(keys, KEY_ATCK2,          PHYS_INPUT_BUTTON_ATCK2(this));
        CS(this).pressedkeys = keys; // store for other users
@@ -2045,7 +1887,7 @@ void ShowRespawnCountdown(entity this)
 .bool team_selected;
 bool ShowTeamSelection(entity this)
 {
-       if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0)
+       if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
                return false;
        stuffcmd(this, "menu_showteamselect\n");
        return true;
@@ -2056,7 +1898,7 @@ void Join(entity this)
 
        if(!this.team_selected)
        if(autocvar_g_campaign || autocvar_g_balance_teams)
-               JoinBestTeam(this, true);
+               TeamBalance_JoinBestTeam(this);
 
        if(autocvar_g_campaign)
                campaign_bots_may_start = true;
@@ -2067,12 +1909,21 @@ void Join(entity this)
 
        if(IS_PLAYER(this))
        if(teamplay && this.team != -1)
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
+       {
+       }
        else
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
        this.team_selected = false;
 }
 
+int GetPlayerLimit()
+{
+       int player_limit = autocvar_g_maxplayers;
+       MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
+       player_limit = M_ARGV(0, int);
+       return player_limit;
+}
+
 /**
  * Determines whether the player is allowed to join. This depends on cvar
  * g_maxplayers, if it isn't used this function always return true, otherwise
@@ -2091,7 +1942,7 @@ int nJoinAllowed(entity this, entity ignore)
                        return 0;
        }
 
-       if(this && this.team_forced < 0)
+       if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                return 0; // forced spectators can never join
 
        // TODO simplify this
@@ -2105,11 +1956,13 @@ int nJoinAllowed(entity this, entity ignore)
                        ++currentlyPlaying;
        });
 
+       int player_limit = GetPlayerLimit();
+
        float free_slots = 0;
-       if (!autocvar_g_maxplayers)
+       if (!player_limit)
                free_slots = maxclients - totalClients;
-       else if(currentlyPlaying < autocvar_g_maxplayers)
-               free_slots = min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying);
+       else if(currentlyPlaying < player_limit)
+               free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
 
        static float join_prevent_msg_time = 0;
        if(this && ignore && !free_slots && time > join_prevent_msg_time)
@@ -2566,16 +2419,16 @@ void PlayerPreThink (entity this)
 
        if(IS_PLAYER(this))
        {
-               if (STAT(FROZEN, this) == 2)
+               if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
                        SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
                        this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
 
                        if (STAT(REVIVE_PROGRESS, this) >= 1)
-                               Unfreeze(this);
+                               Unfreeze(this, false);
                }
-               else if (STAT(FROZEN, this) == 3)
+               else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
                        SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
@@ -2588,7 +2441,7 @@ void PlayerPreThink (entity this)
                                        this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
                        }
                        else if (STAT(REVIVE_PROGRESS, this) <= 0)
-                               Unfreeze(this);
+                               Unfreeze(this, false);
                }
        }
 
@@ -2643,7 +2496,7 @@ void PlayerPreThink (entity this)
                // don't do this in ClientConnect
                // many things can go wrong if a client is spawned as player on connection
                if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
-                       || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+                       || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                                && (!teamplay || autocvar_g_balance_teams)))
                {
                        campaign_bots_may_start = true;
@@ -2724,7 +2577,7 @@ void DrownPlayer(entity this)
 
 void Player_Physics(entity this)
 {
-       set_movetype(this, this.move_movetype);
+       this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
 
        if(!this.move_qcphysics)
                return;
@@ -2829,6 +2682,311 @@ void PlayerPostThink (entity this)
        CSQCMODEL_AUTOUPDATE(this);
 }
 
+/**
+ * message "": do not say, just test flood control
+ * return value:
+ *   1 = accept
+ *   0 = reject
+ *  -1 = fake accept
+ */
+int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
+{
+       if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
+               msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
+
+       if (source)
+               msgin = formatmessage(source, msgin);
+
+       string colorstr;
+       if (!(IS_PLAYER(source) || source.caplayer))
+               colorstr = "^0"; // black for spectators
+       else if(teamplay)
+               colorstr = Team_ColorCode(source.team);
+       else
+       {
+               colorstr = "";
+               teamsay = false;
+       }
+
+       if(game_stopped)
+               teamsay = false;
+
+       if (!source) {
+               colorstr = "";
+               teamsay = false;
+       }
+
+       if(msgin != "")
+               msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
+
+       /*
+        * using bprint solves this... me stupid
+       // how can we prevent the message from appearing in a listen server?
+       // for now, just give "say" back and only handle say_team
+       if(!teamsay)
+       {
+               clientcommand(source, strcat("say ", msgin));
+               return;
+       }
+       */
+
+       string namestr = "";
+       if (source)
+               namestr = playername(source, autocvar_g_chat_teamcolors);
+
+       string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
+
+       string msgstr = "", cmsgstr = "";
+       string privatemsgprefix = string_null;
+       int privatemsgprefixlen = 0;
+       if (msgin != "")
+       {
+               if(privatesay)
+               {
+                       msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
+                       privatemsgprefixlen = strlen(msgstr);
+                       msgstr = strcat(msgstr, msgin);
+                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
+                       privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
+               }
+               else if(teamsay)
+               {
+                       if(strstrofs(msgin, "/me", 0) >= 0)
+                       {
+                               //msgin = strreplace("/me", "", msgin);
+                               //msgin = substring(msgin, 3, strlen(msgin));
+                               msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
+                               msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
+                       }
+                       else
+                               msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+                       cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
+               }
+               else
+               {
+                       if(strstrofs(msgin, "/me", 0) >= 0)
+                       {
+                               //msgin = strreplace("/me", "", msgin);
+                               //msgin = substring(msgin, 3, strlen(msgin));
+                               msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
+                               msgstr = strcat("\{1}^4* ", "^7", msgin);
+                       }
+                       else {
+                               msgstr = "\{1}";
+                               msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
+                               msgstr = strcat(msgstr, msgin);
+                       }
+                       cmsgstr = "";
+               }
+               msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
+       }
+
+       string fullmsgstr = msgstr;
+       string fullcmsgstr = cmsgstr;
+
+       // FLOOD CONTROL
+       int flood = 0;
+       var .float flood_field = floodcontrol_chat;
+       if(floodcontrol && source)
+       {
+               float flood_spl;
+               float flood_burst;
+               float flood_lmax;
+               float lines;
+               if(privatesay)
+               {
+                       flood_spl = autocvar_g_chat_flood_spl_tell;
+                       flood_burst = autocvar_g_chat_flood_burst_tell;
+                       flood_lmax = autocvar_g_chat_flood_lmax_tell;
+                       flood_field = floodcontrol_chattell;
+               }
+               else if(teamsay)
+               {
+                       flood_spl = autocvar_g_chat_flood_spl_team;
+                       flood_burst = autocvar_g_chat_flood_burst_team;
+                       flood_lmax = autocvar_g_chat_flood_lmax_team;
+                       flood_field = floodcontrol_chatteam;
+               }
+               else
+               {
+                       flood_spl = autocvar_g_chat_flood_spl;
+                       flood_burst = autocvar_g_chat_flood_burst;
+                       flood_lmax = autocvar_g_chat_flood_lmax;
+                       flood_field = floodcontrol_chat;
+               }
+               flood_burst = max(0, flood_burst - 1);
+               // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
+
+               // do flood control for the default line size
+               if(msgstr != "")
+               {
+                       getWrappedLine_remaining = msgstr;
+                       msgstr = "";
+                       lines = 0;
+                       while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
+                       {
+                               msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
+                               ++lines;
+                       }
+                       msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
+
+                       if(getWrappedLine_remaining != "")
+                       {
+                               msgstr = strcat(msgstr, "\n");
+                               flood = 2;
+                       }
+
+                       if (time >= source.(flood_field))
+                       {
+                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
+                       }
+                       else
+                       {
+                               flood = 1;
+                               msgstr = fullmsgstr;
+                       }
+               }
+               else
+               {
+                       if (time >= source.(flood_field))
+                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
+                       else
+                               flood = 1;
+               }
+
+               if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
+                       source.(flood_field) = flood = 0;
+       }
+
+       string sourcemsgstr, sourcecmsgstr;
+       if(flood == 2) // cannot happen for empty msgstr
+       {
+               if(autocvar_g_chat_flood_notify_flooder)
+               {
+                       sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
+                       sourcecmsgstr = "";
+               }
+               else
+               {
+                       sourcemsgstr = fullmsgstr;
+                       sourcecmsgstr = fullcmsgstr;
+               }
+               cmsgstr = "";
+       }
+       else
+       {
+               sourcemsgstr = msgstr;
+               sourcecmsgstr = cmsgstr;
+       }
+
+       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+       {
+               if (!game_stopped)
+               if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
+                       teamsay = -1; // spectators
+       }
+
+       if(flood)
+               LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.");
+
+       // build sourcemsgstr by cutting off a prefix and replacing it by the other one
+       if(privatesay)
+               sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
+
+       int ret;
+       if(source && CS(source).muted)
+       {
+               // always fake the message
+               ret = -1;
+       }
+       else if(flood == 1)
+       {
+               if (autocvar_g_chat_flood_notify_flooder)
+               {
+                       sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
+                       ret = 0;
+               }
+               else
+                       ret = -1;
+       }
+       else
+       {
+               ret = 1;
+       }
+
+       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+       {
+               if (!game_stopped)
+               if ((privatesay && (IS_PLAYER(privatesay) || privatesay.caplayer)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
+                       ret = -1; // just hide the message completely
+       }
+
+       MUTATOR_CALLHOOK(ChatMessage, source, ret);
+       ret = M_ARGV(1, int);
+
+       if(sourcemsgstr != "" && ret != 0)
+       {
+               if(ret < 0) // faked message, because the player is muted
+               {
+                       sprint(source, sourcemsgstr);
+                       if(sourcecmsgstr != "" && !privatesay)
+                               centerprint(source, sourcecmsgstr);
+               }
+               else if(privatesay) // private message, between 2 people only
+               {
+                       sprint(source, sourcemsgstr);
+                       if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
+                       if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
+                       {
+                               sprint(privatesay, msgstr);
+                               if(cmsgstr != "")
+                                       centerprint(privatesay, cmsgstr);
+                       }
+               }
+               else if ( teamsay && CS(source).active_minigame )
+               {
+                       sprint(source, sourcemsgstr);
+                       dedicated_print(msgstr); // send to server console too
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+               }
+               else if(teamsay > 0) // team message, only sent to team mates
+               {
+                       sprint(source, sourcemsgstr);
+                       dedicated_print(msgstr); // send to server console too
+                       if(sourcecmsgstr != "")
+                               centerprint(source, sourcecmsgstr);
+                       FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                               if(cmsgstr != "")
+                                       centerprint(it, cmsgstr);
+                       });
+               }
+               else if(teamsay < 0) // spectator message, only sent to spectators
+               {
+                       sprint(source, sourcemsgstr);
+                       dedicated_print(msgstr); // send to server console too
+                       FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+               }
+               else
+               {
+                       if (source) {
+                               sprint(source, sourcemsgstr);
+                               dedicated_print(msgstr); // send to server console too
+                               MX_Say(strcat(playername(source, true), "^7: ", msgin));
+                       }
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+               }
+       }
+
+       return ret;
+}
+
 // hack to copy the button fields from the client entity to the Client State
 void PM_UpdateButtons(entity this, entity store)
 {
@@ -2836,7 +2994,7 @@ void PM_UpdateButtons(entity this, entity store)
                store.impulse = this.impulse;
        this.impulse = 0;
 
-       bool typing = this.buttonchat;
+       bool typing = this.buttonchat || this.button14;
 
        store.button0 = (typing) ? 0 : this.button0;
        //button1?!
index 2d3a099b3988bc715589d9f5018fd310be1fc7f6..f8a7e2ab5fa4063b29ebe4c96c7f32ddfca42127 100644 (file)
@@ -17,7 +17,7 @@ CLASS(Client, Object)
     /** Client IP */
     ATTRIB(Client, netaddress, string, this.netaddress);
     ATTRIB(Client, playermodel, string, this.playermodel);
-    ATTRIB(Client, playerskin, int, this.playerskin);
+    ATTRIB(Client, playerskin, string, this.playerskin);
 
     /** fingerprint of CA key the player used to authenticate */
     ATTRIB(Client, crypto_keyfp, string, this.crypto_keyfp);
@@ -145,6 +145,7 @@ CLASS(Client, Object)
     ATTRIB(Client, cvar_cl_accuracy_data_receive, bool, this.cvar_cl_accuracy_data_receive);
     ATTRIBARRAY(Client, cvar_cl_weaponpriorities, string, 10);
     ATTRIB(Client, cvar_cl_weaponpriority, string, this.cvar_cl_weaponpriority);
+    ATTRIB(Client, cvar_cl_cts_noautoswitch, bool, this.cvar_cl_cts_noautoswitch);
 
     METHOD(Client, m_unwind, bool(Client this));
 
@@ -226,6 +227,8 @@ METHOD(Client, m_unwind, bool(Client this))
     return false;
 }
 
+bool PlayerInList(entity player, string list);
+
 /// \brief Print the string to the client's chat.
 /// \param[in] client Client to print to.
 /// \param[in] text Text to print.
@@ -274,10 +277,12 @@ void FixPlayermodel(entity player);
 
 void ClientInit_misc(entity this);
 
-void ClientKill_TeamChange(entity this, float targetteam);  // 0 = don't change, -1 = auto, -2 = spec
+int GetPlayerLimit();
 
 bool joinAllowed(entity this);
 void Join(entity this);
 
 #define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee)
 #define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); }
+
+int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
diff --git a/qcsrc/server/clientkill.qc b/qcsrc/server/clientkill.qc
new file mode 100644 (file)
index 0000000..08758a0
--- /dev/null
@@ -0,0 +1,202 @@
+#include "clientkill.qh"
+
+#include <server/defs.qh>
+
+#include "g_damage.qh"
+#include "teamplay.qh"
+
+#include <common/vehicles/sv_vehicles.qh>
+#include <common/notifications/all.qh>
+#include <common/stats.qh>
+
+void ClientKill_Now_TeamChange(entity this)
+{
+       if (this.killindicator_teamchange == -1)
+       {
+               TeamBalance_JoinBestTeam(this);
+       }
+       else if (this.killindicator_teamchange == -2)
+       {
+               if (blockSpectators)
+                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
+               PutObserverInServer(this);
+       }
+       else
+       {
+               Player_SetTeamIndexChecked(this, Team_TeamToIndex(
+                       this.killindicator_teamchange));
+       }
+       this.killindicator_teamchange = 0;
+}
+
+void ClientKill_Now(entity this)
+{
+       if (this.vehicle)
+       {
+               vehicles_exit(this.vehicle, VHEF_RELEASE);
+               if (!this.killindicator_teamchange)
+               {
+                       this.vehicle_health = -1;
+                       Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+               }
+       }
+
+       if (this.killindicator && !wasfreed(this.killindicator))
+               delete(this.killindicator);
+
+       this.killindicator = NULL;
+
+       if (this.killindicator_teamchange)
+               ClientKill_Now_TeamChange(this);
+
+       if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
+       {
+               Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+       }
+
+       // now I am sure the player IS dead
+}
+void KillIndicator_Think(entity this)
+{
+       if (game_stopped || (this.owner.alpha < 0 && !this.owner.vehicle))
+       {
+               this.owner.killindicator = NULL;
+               delete(this);
+               return;
+       }
+
+       if (this.cnt <= 0)
+       {
+               ClientKill_Now(this.owner);
+               return;
+       }
+
+       // count == 1 means that it's silent
+       if (this.count != 1)
+       {
+               if (this.cnt <= 10)
+                       setmodel(this, MDL_NUM(this.cnt));
+               if (IS_REAL_CLIENT(this.owner))
+               {
+                       if (this.cnt <= 10)
+                               Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt));
+               }
+       }
+       this.nextthink = time + 1;
+       this.cnt -= 1;
+}
+
+.float lip;
+float clientkilltime;
+.float clientkill_nexttime;
+void ClientKill_TeamChange(entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec
+{
+       if (game_stopped)
+               return;
+
+       float killtime = autocvar_g_balance_kill_delay;
+
+       if (MUTATOR_CALLHOOK(ClientKill, this, killtime))
+               return;
+       killtime = M_ARGV(1, float);
+
+       this.killindicator_teamchange = targetteam;
+
+       // this.killindicator.count == 1 means that the kill indicator was spawned by ClientKill_Silent
+       if(killtime <= 0 && this.killindicator && this.killindicator.count == 1)
+       {
+               ClientKill_Now(this); // allow instant kill in this case
+               return;
+       }
+
+       if (!this.killindicator)
+       {
+               if (!IS_DEAD(this))
+               {
+                       killtime = max(killtime, this.clientkill_nexttime - time);
+                       this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
+               }
+
+               if (killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this))
+               {
+                       ClientKill_Now(this);
+               }
+               else
+               {
+                       float starttime = max(time, clientkilltime);
+
+                       this.killindicator = spawn();
+                       this.killindicator.owner = this;
+                       this.killindicator.scale = 0.5;
+                       setattachment(this.killindicator, this, "");
+                       setorigin(this.killindicator, '0 0 52');
+                       setthink(this.killindicator, KillIndicator_Think);
+                       this.killindicator.nextthink = starttime + (this.lip) * 0.05;
+                       clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05);
+                       this.killindicator.cnt = ceil(killtime);
+                       this.killindicator.count = bound(0, ceil(killtime), 10);
+                       //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n"));
+
+                       IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST),
+                       {
+                               it.killindicator = spawn();
+                               it.killindicator.owner = it;
+                               it.killindicator.scale = 0.5;
+                               setattachment(it.killindicator, it, "");
+                               setorigin(it.killindicator, '0 0 52');
+                               setthink(it.killindicator, KillIndicator_Think);
+                               it.killindicator.nextthink = starttime + (it.lip) * 0.05;
+                               //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05);
+                               it.killindicator.cnt = ceil(killtime);
+                       });
+                       this.lip = 0;
+               }
+       }
+       if (this.killindicator)
+       {
+               Notification notif;
+               if (targetteam == 0) // just die
+               {
+                       this.killindicator.colormod = '0 0 0';
+                       notif = CENTER_TEAMCHANGE_SUICIDE;
+               }
+               else if (targetteam == -1) // auto
+               {
+                       this.killindicator.colormod = '0 1 0';
+                       notif = CENTER_TEAMCHANGE_AUTO;
+               }
+               else if (targetteam == -2) // spectate
+               {
+                       this.killindicator.colormod = '0.5 0.5 0.5';
+                       notif = CENTER_TEAMCHANGE_SPECTATE;
+               }
+               else
+               {
+                       this.killindicator.colormod = Team_ColorRGB(targetteam);
+                       notif = APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE);
+               }
+               if (IS_REAL_CLIENT(this) && this.killindicator.cnt > 0)
+                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, notif, this.killindicator.cnt);
+       }
+
+}
+
+void ClientKill_Silent(entity this, float _delay)
+{
+       this.killindicator = spawn();
+       this.killindicator.owner = this;
+       setthink(this.killindicator, KillIndicator_Think);
+       this.killindicator.nextthink = time + (this.lip) * 0.05;
+       this.killindicator.cnt = ceil(_delay);
+       this.killindicator.count = 1; // this is used to indicate that it should be silent
+       this.lip = 0;
+}
+
+// Called when a client types 'kill' in the console
+void ClientKill(entity this)
+{
+       if (game_stopped || this.player_blocked || STAT(FROZEN, this))
+               return;
+
+       ClientKill_TeamChange(this, 0);
+}
diff --git a/qcsrc/server/clientkill.qh b/qcsrc/server/clientkill.qh
new file mode 100644 (file)
index 0000000..5b26adf
--- /dev/null
@@ -0,0 +1,11 @@
+
+// set when showing a kill countdown
+.entity killindicator;
+.int killindicator_teamchange;
+
+void ClientKill_Now_TeamChange(entity this);
+void ClientKill_Now(entity this);
+void KillIndicator_Think(entity this);
+void ClientKill_TeamChange(entity this, float targetteam);  // 0 = don't change, -1 = auto, -2 = spec
+void ClientKill_Silent(entity this, float _delay);
+void ClientKill(entity this);
index da495f7962f89210697c4dead7802da99df03d78..fb63b057e23c1cc250da3741ae7fcf65af058ac9 100644 (file)
@@ -18,7 +18,7 @@
 //  Last updated: December 29th, 2011
 // =====================================================
 
-void BanCommand_ban(float request, float argc, string command)
+void BanCommand_ban(int request, int argc, string command)
 {
        switch (request)
        {
@@ -54,7 +54,7 @@ void BanCommand_ban(float request, float argc, string command)
        }
 }
 
-void BanCommand_banlist(float request)
+void BanCommand_banlist(int request)
 {
        switch (request)
        {
@@ -75,7 +75,7 @@ void BanCommand_banlist(float request)
        }
 }
 
-void BanCommand_kickban(float request, float argc, string command)
+void BanCommand_kickban(int request, int argc, string command)
 {
        switch (request)
        {
@@ -122,7 +122,7 @@ void BanCommand_kickban(float request, float argc, string command)
        }
 }
 
-void BanCommand_mute(float request, float argc, string command)  // TODO: Add a sort of mute-"ban" which allows players to be muted based on IP/cryptokey
+void BanCommand_mute(int request, int argc, string command)  // TODO: Add a sort of mute-"ban" which allows players to be muted based on IP/cryptokey
 {
        switch (request)
        {
@@ -157,7 +157,7 @@ void BanCommand_mute(float request, float argc, string command)  // TODO: Add a
        }
 }
 
-void BanCommand_unban(float request, float argc)
+void BanCommand_unban(int request, int argc)
 {
        switch (request)
        {
@@ -203,7 +203,7 @@ void BanCommand_unban(float request, float argc)
        }
 }
 
-void BanCommand_unmute(float request, float argc)
+void BanCommand_unmute(int request, int argc)
 {
        switch (request)
        {
@@ -240,7 +240,7 @@ void BanCommand_unmute(float request, float argc)
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void BanCommand_(float request)
+void BanCommand_(int request)
 {
     switch(request)
     {
@@ -285,7 +285,7 @@ void BanCommand_macro_help()
 #undef BAN_COMMAND
 }
 
-float BanCommand_macro_command(float argc, string command)
+float BanCommand_macro_command(int argc, string command)
 {
        #define BAN_COMMAND(name, function, description) \
                { if (name == strtolower(argv(0))) { function; return true; } }
@@ -296,7 +296,7 @@ float BanCommand_macro_command(float argc, string command)
        return false;
 }
 
-float BanCommand_macro_usage(float argc)
+float BanCommand_macro_usage(int argc)
 {
        #define BAN_COMMAND(name, function, description) \
                { if (name == strtolower(argv(1))) { function; return true; } }
@@ -318,7 +318,7 @@ void BanCommand_macro_write_aliases(float fh)
 
 float BanCommand(string command)
 {
-       float argc = tokenize_console(command);
+       int argc = tokenize_console(command);
 
        // Guide for working with argc arguments by example:
        // argc:   1    - 2      - 3     - 4
index 3cbaaf978e2950d7ad241a4147fab59953db3c11..ae16532ecfb37d77fbbc3e38ec32a814c0cfd6f0 100644 (file)
@@ -12,4 +12,4 @@
 void BanCommand_macro_write_aliases(float fh);
 
 void BanCommand_macro_help();
-float BanCommand_macro_usage(float argc);
+float BanCommand_macro_usage(int argc);
index 9b7a0f6574fb78cf3636d8325b4bb7479f59ae47..80716d811c8e1da75124b95edb4a23b2e2ea5150 100644 (file)
@@ -8,9 +8,12 @@
 #include "common.qh"
 #include "vote.qh"
 
+#include "../bot/api.qh"
+
 #include "../campaign.qh"
 #include "../cheats.qh"
 #include "../client.qh"
+#include "../clientkill.qh"
 #include "../player.qh"
 #include "../ipban.qh"
 #include "../mapvoting.qh"
@@ -72,7 +75,7 @@ bool SV_ParseClientCommand_floodcheck(entity this)
 //  Command Sub-Functions
 // =======================
 
-void ClientCommand_autoswitch(entity caller, float request, float argc)
+void ClientCommand_autoswitch(entity caller, int request, int argc)
 {
        switch (request)
        {
@@ -97,7 +100,7 @@ void ClientCommand_autoswitch(entity caller, float request, float argc)
        }
 }
 
-void ClientCommand_clientversion(entity caller, float request, float argc)  // internal command, used only by code
+void ClientCommand_clientversion(entity caller, int request, int argc)  // internal command, used only by code
 {
        switch (request)
        {
@@ -118,7 +121,7 @@ void ClientCommand_clientversion(entity caller, float request, float argc)  // i
                                        {
                                                // JoinBestTeam(caller, false, true);
                                        }
-                                       else if (teamplay && !autocvar_sv_spectate && !(caller.team_forced > 0))
+                                       else if (teamplay && !autocvar_sv_spectate && !(Player_GetForcedTeamIndex(caller) > 0))
                                        {
                                                TRANSMUTE(Observer, caller);  // really?
                                                stuffcmd(caller, "menu_showteamselect\n");
@@ -140,7 +143,7 @@ void ClientCommand_clientversion(entity caller, float request, float argc)  // i
        }
 }
 
-void ClientCommand_mv_getpicture(entity caller, float request, float argc)  // internal command, used only by code
+void ClientCommand_mv_getpicture(entity caller, int request, int argc)  // internal command, used only by code
 {
        switch (request)
        {
@@ -165,7 +168,62 @@ void ClientCommand_mv_getpicture(entity caller, float request, float argc)  // i
        }
 }
 
-void ClientCommand_join(entity caller, float request)
+void ClientCommand_wpeditor(entity caller, int request, int argc)
+{
+       switch (request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       if (!autocvar_g_waypointeditor)
+                       {
+                               sprint(caller, "ERROR: this command works only if the waypoint editor is on\n");
+                               return;
+                       }
+
+                       if (argv(1) != "")
+                       {
+                               if (argv(1) == "spawn")
+                               {
+                                       if (!IS_PLAYER(caller))
+                                               sprint(caller, "ERROR: this command works only if you are player\n");
+                                       else
+                                               waypoint_spawn_fromeditor(caller);
+                               }
+                               else if (argv(1) == "remove")
+                               {
+                                       if (!IS_PLAYER(caller))
+                                               sprint(caller, "ERROR: this command works only if you are player\n");
+                                       else
+                                               waypoint_remove_fromeditor(caller);
+                               }
+                               else if (argv(1) == "unreachable")
+                               {
+                                       if (!IS_PLAYER(caller))
+                                               sprint(caller, "ERROR: this command works only if you are player\n");
+                                       else
+                                               waypoint_unreachable(caller);
+                               }
+                               else if (argv(1) == "saveall")
+                                       waypoint_saveall();
+                               else if (argv(1) == "relinkall")
+                                       waypoint_schedulerelinkall();
+
+                               return;
+                       }
+               }
+
+               default:
+                       sprint(caller, "Incorrect parameters for ^2wpeditor^7\n");
+               case CMD_REQUEST_USAGE:
+               {
+                       sprint(caller, "\nUsage:^3 cmd wpeditor action\n");
+                       sprint(caller, "  Where 'action' can be: spawn, remove, unreachable, saveall, relinkall\n");
+                       return;
+               }
+       }
+}
+
+void ClientCommand_join(entity caller, int request)
 {
        switch (request)
        {
@@ -189,7 +247,37 @@ void ClientCommand_join(entity caller, float request)
        }
 }
 
-void ClientCommand_physics(entity caller, float request, float argc)
+void ClientCommand_kill(entity caller, int request)
+{
+       switch (request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       if(IS_SPEC(caller) || IS_OBSERVER(caller))
+                               return; // no point warning about this, command does nothing
+
+                       if(GetResourceAmount(caller, RESOURCE_HEALTH) <= 0)
+                       {
+                               sprint(caller, "Can't die - you are already dead!\n");
+                               return;
+                       }
+
+                       ClientKill(caller);
+
+                       return;  // never fall through to usage
+               }
+
+               default:
+               case CMD_REQUEST_USAGE:
+               {
+                       sprint(caller, "\nUsage:^3 cmd kill\n");
+                       sprint(caller, "  No arguments required.\n");
+                       return;
+               }
+       }
+}
+
+void ClientCommand_physics(entity caller, int request, int argc)
 {
        switch (request)
        {
@@ -229,7 +317,7 @@ void ClientCommand_physics(entity caller, float request, float argc)
        }
 }
 
-void ClientCommand_ready(entity caller, float request)  // todo: anti-spam for toggling readyness
+void ClientCommand_ready(entity caller, int request)  // todo: anti-spam for toggling readyness
 {
        switch (request)
        {
@@ -278,7 +366,7 @@ void ClientCommand_ready(entity caller, float request)  // todo: anti-spam for t
        }
 }
 
-void ClientCommand_say(entity caller, float request, float argc, string command)
+void ClientCommand_say(entity caller, int request, int argc, string command)
 {
        switch (request)
        {
@@ -298,13 +386,14 @@ void ClientCommand_say(entity caller, float request, float argc, string command)
        }
 }
 
-void ClientCommand_say_team(entity caller, float request, float argc, string command)
+void ClientCommand_say_team(entity caller, int request, int argc, string command)
 {
        switch (request)
        {
                case CMD_REQUEST_COMMAND:
                {
-                       if (argc >= 2)   Say(caller, true, NULL, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
+                       if (argc >= 2)
+                               Say(caller, true, NULL, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
                        return;  // never fall through to usage
                }
 
@@ -319,7 +408,7 @@ void ClientCommand_say_team(entity caller, float request, float argc, string com
 }
 
 .bool team_selected;
-void ClientCommand_selectteam(entity caller, float request, float argc)
+void ClientCommand_selectteam(entity caller, int request, int argc)
 {
        switch (request)
        {
@@ -338,7 +427,7 @@ void ClientCommand_selectteam(entity caller, float request, float argc)
                                sprint(caller, "^7selectteam can only be used in teamgames\n");
                                return;
                        }
-                       if (caller.team_forced > 0)
+                       if (Player_GetForcedTeamIndex(caller) > 0)
                        {
                                sprint(caller, "^7selectteam can not be used as your team is forced\n");
                                return;
@@ -394,13 +483,16 @@ void ClientCommand_selectteam(entity caller, float request, float argc)
                        if ((selection != -1) && autocvar_g_balance_teams &&
                                autocvar_g_balance_teams_prevent_imbalance)
                        {
-                               CheckAllowedTeams(caller);
-                               GetTeamCounts(caller);
-                               if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0)
+                               entity balance = TeamBalance_CheckAllowedTeams(caller);
+                               TeamBalance_GetTeamCounts(balance, caller);
+                               if ((Team_IndexToBit(Team_TeamToIndex(selection)) &
+                                       TeamBalance_FindBestTeams(balance, caller, false)) == 0)
                                {
                                        Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
+                                       TeamBalance_Destroy(balance);
                                        return;
                                }
+                               TeamBalance_Destroy(balance);
                        }
                        ClientKill_TeamChange(caller, selection);
                        if (!IS_PLAYER(caller))
@@ -421,7 +513,7 @@ void ClientCommand_selectteam(entity caller, float request, float argc)
        }
 }
 
-void ClientCommand_selfstuff(entity caller, float request, string command)
+void ClientCommand_selfstuff(entity caller, int request, string command)
 {
        switch (request)
        {
@@ -445,7 +537,7 @@ void ClientCommand_selfstuff(entity caller, float request, string command)
        }
 }
 
-void ClientCommand_sentcvar(entity caller, float request, float argc, string command)
+void ClientCommand_sentcvar(entity caller, int request, int argc, string command)
 {
        switch (request)
        {
@@ -479,7 +571,7 @@ void ClientCommand_sentcvar(entity caller, float request, float argc, string com
        }
 }
 
-void ClientCommand_spectate(entity caller, float request)
+void ClientCommand_spectate(entity caller, int request)
 {
        switch (request)
        {
@@ -519,7 +611,7 @@ void ClientCommand_spectate(entity caller, float request)
        }
 }
 
-void ClientCommand_suggestmap(entity caller, float request, float argc)
+void ClientCommand_suggestmap(entity caller, int request, int argc)
 {
        switch (request)
        {
@@ -543,7 +635,7 @@ void ClientCommand_suggestmap(entity caller, float request, float argc)
        }
 }
 
-void ClientCommand_tell(entity caller, float request, float argc, string command)
+void ClientCommand_tell(entity caller, int request, int argc, string command)
 {
        switch (request)
        {
@@ -600,7 +692,7 @@ void ClientCommand_tell(entity caller, float request, float argc, string command
        }
 }
 
-void ClientCommand_voice(entity caller, float request, float argc, string command)
+void ClientCommand_voice(entity caller, int request, int argc, string command)
 {
        switch (request)
        {
@@ -637,7 +729,7 @@ void ClientCommand_voice(entity caller, float request, float argc, string comman
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void ClientCommand_(entity caller, float request)
+void ClientCommand_(entity caller, int request)
 {
     switch(request)
     {
@@ -667,8 +759,10 @@ void ClientCommand_(entity caller, float request)
 #define CLIENT_COMMANDS(ent, request, arguments, command) \
        CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(ent, request, arguments), "Whether or not to switch automatically when getting a better weapon") \
        CLIENT_COMMAND("clientversion", ClientCommand_clientversion(ent, request, arguments), "Release version of the game") \
-       CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(ent, request, arguments), "Retrieve mapshot picture from the server") \
        CLIENT_COMMAND("join", ClientCommand_join(ent, request), "Become a player in the game") \
+       CLIENT_COMMAND("kill", ClientCommand_kill(ent, request), "Become a member of the dead") \
+       CLIENT_COMMAND("minigame", ClientCommand_minigame(ent, request, arguments, command), "Start a minigame") \
+       CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(ent, request, arguments), "Retrieve mapshot picture from the server") \
        CLIENT_COMMAND("physics", ClientCommand_physics(ent, request, arguments), "Change physics set") \
        CLIENT_COMMAND("ready", ClientCommand_ready(ent, request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
        CLIENT_COMMAND("say", ClientCommand_say(ent, request, arguments, command), "Print a message to chat to all players") \
@@ -680,7 +774,7 @@ void ClientCommand_(entity caller, float request)
        CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(ent, request, arguments), "Suggest a map to the mapvote at match end") \
        CLIENT_COMMAND("tell", ClientCommand_tell(ent, request, arguments, command), "Send a message directly to a player") \
        CLIENT_COMMAND("voice", ClientCommand_voice(ent, request, arguments, command), "Send voice message via sound") \
-       CLIENT_COMMAND("minigame", ClientCommand_minigame(ent, request, arguments, command), "Start a minigame") \
+       CLIENT_COMMAND("wpeditor", ClientCommand_wpeditor(ent, request, arguments), "Waypoint editor commands") \
        /* nothing */
 
 void ClientCommand_macro_help(entity caller)
@@ -692,7 +786,7 @@ void ClientCommand_macro_help(entity caller)
 #undef CLIENT_COMMAND
 }
 
-float ClientCommand_macro_command(float argc, entity caller, string command)
+float ClientCommand_macro_command(int argc, entity caller, string command)
 {
        #define CLIENT_COMMAND(name, function, description) \
                { if (name == strtolower(argv(0))) { function; return true; } }
@@ -703,7 +797,7 @@ float ClientCommand_macro_command(float argc, entity caller, string command)
        return false;
 }
 
-float ClientCommand_macro_usage(float argc, entity caller)
+float ClientCommand_macro_usage(int argc, entity caller)
 {
        #define CLIENT_COMMAND(name, function, description) \
                { if (name == strtolower(argv(1))) { function; return true; } }
@@ -741,7 +835,7 @@ void SV_ParseClientCommand(entity this, string command)
        // if we're banned, don't even parse the command
        if (Ban_MaybeEnforceBanOnce(this)) return;
 
-       float argc = tokenize_console(command);
+       int argc = tokenize_console(command);
 
        // Guide for working with argc arguments by example:
        // argc:   1    - 2      - 3     - 4
@@ -755,6 +849,7 @@ void SV_ParseClientCommand(entity this, string command)
                case "begin": break;                               // handled by engine in host_cmd.c
                case "download": break;                            // handled by engine in cl_parse.c
                case "mv_getpicture": break;                       // handled by server in this file
+               case "wpeditor": break;                            // handled by server in this file
                case "pause": break;                               // handled by engine in host_cmd.c
                case "prespawn": break;                            // handled by engine in host_cmd.c
                case "sentcvar": break;                            // handled by server in this file
index cb8ab239fbebd1ac74b4781982d3d93ed14f43ac..643afa3f177887ff8ab2754a30c882eb90263231 100644 (file)
@@ -76,7 +76,7 @@ float VerifyClientNumber(float tmp_number)
        else return true;
 }
 
-entity GetIndexedEntity(float argc, float start_index)
+entity GetIndexedEntity(int argc, float start_index)
 {
        entity selection;
        float tmp_number, index;
@@ -280,7 +280,7 @@ void timeout_handler_think(entity this)
 //  Common commands used in both sv_cmd.qc and cmd.qc
 // ===================================================
 
-void CommonCommand_cvar_changes(float request, entity caller)
+void CommonCommand_cvar_changes(int request, entity caller)
 {
        switch (request)
        {
@@ -301,7 +301,7 @@ void CommonCommand_cvar_changes(float request, entity caller)
        }
 }
 
-void CommonCommand_cvar_purechanges(float request, entity caller)
+void CommonCommand_cvar_purechanges(int request, entity caller)
 {
        switch (request)
        {
@@ -469,7 +469,7 @@ void CommonCommand_editmob(int request, entity caller, int argc)
        }
 }
 
-void CommonCommand_info(float request, entity caller, float argc)
+void CommonCommand_info(int request, entity caller, int argc)
 {
        switch (request)
        {
@@ -493,7 +493,7 @@ void CommonCommand_info(float request, entity caller, float argc)
        }
 }
 
-void CommonCommand_ladder(float request, entity caller)
+void CommonCommand_ladder(int request, entity caller)
 {
        switch (request)
        {
@@ -513,7 +513,7 @@ void CommonCommand_ladder(float request, entity caller)
        }
 }
 
-void CommonCommand_lsmaps(float request, entity caller)
+void CommonCommand_lsmaps(int request, entity caller)
 {
        switch (request)
        {
@@ -533,7 +533,7 @@ void CommonCommand_lsmaps(float request, entity caller)
        }
 }
 
-void CommonCommand_printmaplist(float request, entity caller)
+void CommonCommand_printmaplist(int request, entity caller)
 {
        switch (request)
        {
@@ -553,7 +553,7 @@ void CommonCommand_printmaplist(float request, entity caller)
        }
 }
 
-void CommonCommand_rankings(float request, entity caller)
+void CommonCommand_rankings(int request, entity caller)
 {
        switch (request)
        {
@@ -573,7 +573,7 @@ void CommonCommand_rankings(float request, entity caller)
        }
 }
 
-void CommonCommand_records(float request, entity caller)
+void CommonCommand_records(int request, entity caller)
 {
        switch (request)
        {
@@ -601,7 +601,7 @@ void CommonCommand_records(float request, entity caller)
        }
 }
 
-void CommonCommand_teamstatus(float request, entity caller)
+void CommonCommand_teamstatus(int request, entity caller)
 {
        switch (request)
        {
@@ -621,7 +621,7 @@ void CommonCommand_teamstatus(float request, entity caller)
        }
 }
 
-void CommonCommand_time(float request, entity caller)
+void CommonCommand_time(int request, entity caller)
 {
        switch (request)
        {
@@ -632,8 +632,8 @@ void CommonCommand_time(float request, entity caller)
                        print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
                        print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
                        print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
-                       print_to(caller, strcat("localtime = ", strftime(true, "%a %b %e %H:%M:%S %Z %Y")));
-                       print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %e %H:%M:%S %Z %Y")));
+                       print_to(caller, strcat("localtime = ", strftime(true, "%a %b %d %H:%M:%S %Z %Y")));
+                       print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %d %H:%M:%S %Z %Y")));
                        return;
                }
 
@@ -647,7 +647,7 @@ void CommonCommand_time(float request, entity caller)
        }
 }
 
-void CommonCommand_timein(float request, entity caller)
+void CommonCommand_timein(int request, entity caller)
 {
        switch (request)
        {
@@ -702,7 +702,7 @@ void CommonCommand_timein(float request, entity caller)
        }
 }
 
-void CommonCommand_timeout(float request, entity caller)  // DEAR GOD THIS COMMAND IS TERRIBLE.
+void CommonCommand_timeout(int request, entity caller)  // DEAR GOD THIS COMMAND IS TERRIBLE.
 {
        switch (request)
        {
@@ -771,7 +771,7 @@ void CommonCommand_timeout(float request, entity caller)  // DEAR GOD THIS COMMA
        }
 }
 
-void CommonCommand_who(float request, entity caller, float argc)
+void CommonCommand_who(int request, entity caller, int argc)
 {
        switch (request)
        {
@@ -836,7 +836,7 @@ void CommonCommand_who(float request, entity caller, float argc)
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void CommonCommand_(float request, entity caller)
+void CommonCommand_(int request, entity caller)
 {
     switch(request)
     {
index 13cbfe49e127ece70856a11fcd58f670c5750fa6..f03a815de26b6de896ddee64a71dc6e05e0476c2 100644 (file)
@@ -81,7 +81,7 @@ string GetClientErrorString_color(float clienterror, string original_input, stri
 // is this entity number even in the possible range of entities?
 float VerifyClientNumber(float tmp_number);
 
-entity GetIndexedEntity(float argc, float start_index);
+entity GetIndexedEntity(int argc, float start_index);
 
 // find a player which matches the input string, and return their entity
 entity GetFilteredEntity(string input);
@@ -105,33 +105,33 @@ void timeout_handler_think(entity this);
 //  Common commands used in both sv_cmd.qc and cmd.qc
 // ===================================================
 
-void CommonCommand_cvar_changes(float request, entity caller);
+void CommonCommand_cvar_changes(int request, entity caller);
 
-void CommonCommand_cvar_purechanges(float request, entity caller);
+void CommonCommand_cvar_purechanges(int request, entity caller);
 
-void CommonCommand_editmob(float request, entity caller, float argc);
+void CommonCommand_editmob(int request, entity caller, int argc);
 
-void CommonCommand_info(float request, entity caller, float argc);
+void CommonCommand_info(int request, entity caller, int argc);
 
-void CommonCommand_ladder(float request, entity caller);
+void CommonCommand_ladder(int request, entity caller);
 
-void CommonCommand_lsmaps(float request, entity caller);
+void CommonCommand_lsmaps(int request, entity caller);
 
-void CommonCommand_printmaplist(float request, entity caller);
+void CommonCommand_printmaplist(int request, entity caller);
 
-void CommonCommand_rankings(float request, entity caller);
+void CommonCommand_rankings(int request, entity caller);
 
-void CommonCommand_records(float request, entity caller);
+void CommonCommand_records(int request, entity caller);
 
-void CommonCommand_teamstatus(float request, entity caller);
+void CommonCommand_teamstatus(int request, entity caller);
 
-void CommonCommand_time(float request, entity caller);
+void CommonCommand_time(int request, entity caller);
 
-void CommonCommand_timein(float request, entity caller);
+void CommonCommand_timein(int request, entity caller);
 
-void CommonCommand_timeout(float request, entity caller);
+void CommonCommand_timeout(int request, entity caller);
 
-void CommonCommand_who(float request, entity caller, float argc);
+void CommonCommand_who(int request, entity caller, int argc);
 
 
 // ==================================
@@ -160,7 +160,7 @@ void CommonCommand_macro_help(entity caller)
        FOREACH(COMMON_COMMANDS, true, { print_to(caller, sprintf("  ^2%s^7: %s", it.m_name, it.m_description)); });
 }
 
-float CommonCommand_macro_command(float argc, entity caller, string command)
+float CommonCommand_macro_command(int argc, entity caller, string command)
 {
        string c = strtolower(argv(0));
        FOREACH(COMMON_COMMANDS, it.m_name == c, {
@@ -170,7 +170,7 @@ float CommonCommand_macro_command(float argc, entity caller, string command)
        return false;
 }
 
-float CommonCommand_macro_usage(float argc, entity caller)
+float CommonCommand_macro_usage(int argc, entity caller)
 {
        string c = strtolower(argv(1));
        FOREACH(COMMON_COMMANDS, it.m_name == c, {
index 87bcef82f7124e1f001603129320b0684d46af5f..e3dbf795f60541f9482f4997c42b28b540552837 100644 (file)
@@ -366,7 +366,7 @@ void RadarMap_Think(entity this)
        }
 }
 
-bool RadarMap_Make(float argc)
+bool RadarMap_Make(int argc)
 {
        float i;
 
index ffa82e5c02e09131efb74f5fd150fba0b4e601ec..44bd8fb4bac922794440153d22b1ee0ac9422d13 100644 (file)
@@ -1,6 +1,6 @@
 #pragma once
 #ifndef RADARMAP
-bool RadarMap_Make(float argc) { LOG_INFO("radarmap is disabled, compile with -DRADARMAP to enable it."); return true; }
+bool RadarMap_Make(int argc) { LOG_INFO("radarmap is disabled, compile with -DRADARMAP to enable it."); return true; }
 #else
 
 // ===========================================
@@ -17,6 +17,6 @@ string doublehex = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D
 // FF is contained twice, to map 256 to FF too
 // removes the need to bound()
 
-bool RadarMap_Make(float argc);
+bool RadarMap_Make(int argc);
 
 #endif
index 1076225d82acaf1e89a00432927c4c95b8593c34..c3e5bf9015293c712bb59f4e1d05c6c71f626804 100644 (file)
@@ -88,7 +88,7 @@ void changematchtime(float delta, float mi, float ma)
 //  Command Sub-Functions
 // =======================
 
-void GameCommand_adminmsg(float request, float argc)
+void GameCommand_adminmsg(int request, int argc)
 {
        switch (request)
        {
@@ -160,7 +160,7 @@ void GameCommand_adminmsg(float request, float argc)
        }
 }
 
-void GameCommand_allready(float request)
+void GameCommand_allready(int request)
 {
        switch (request)
        {
@@ -180,7 +180,7 @@ void GameCommand_allready(float request)
        }
 }
 
-void GameCommand_allspec(float request, float argc)
+void GameCommand_allspec(int request, int argc)
 {
        switch (request)
        {
@@ -209,7 +209,7 @@ void GameCommand_allspec(float request, float argc)
        }
 }
 
-void GameCommand_anticheat(float request, float argc)
+void GameCommand_anticheat(int request, int argc)
 {
        switch (request)
        {
@@ -240,7 +240,7 @@ void GameCommand_anticheat(float request, float argc)
        }
 }
 
-void GameCommand_bbox(float request)
+void GameCommand_bbox(int request)
 {
        switch (request)
        {
@@ -313,7 +313,7 @@ void GameCommand_bbox(float request)
        }
 }
 
-void GameCommand_bot_cmd(float request, float argc, string command)
+void GameCommand_bot_cmd(int request, int argc, string command)
 {
        switch (request)
        {
@@ -445,7 +445,7 @@ void GameCommand_bot_cmd(float request, float argc, string command)
        }
 }
 
-void GameCommand_cointoss(float request, float argc)
+void GameCommand_cointoss(int request, int argc)
 {
        switch (request)
        {
@@ -469,7 +469,7 @@ void GameCommand_cointoss(float request, float argc)
        }
 }
 
-void GameCommand_database(float request, float argc)
+void GameCommand_database(int request, int argc)
 {
        switch (request)
        {
@@ -512,7 +512,7 @@ void GameCommand_database(float request, float argc)
        }
 }
 
-void GameCommand_defer_clear(float request, float argc)
+void GameCommand_defer_clear(int request, int argc)
 {
        switch (request)
        {
@@ -549,14 +549,14 @@ void GameCommand_defer_clear(float request, float argc)
        }
 }
 
-void GameCommand_defer_clear_all(float request)
+void GameCommand_defer_clear_all(int request)
 {
        switch (request)
        {
                case CMD_REQUEST_COMMAND:
                {
                        int n = 0;
-                       float argc;
+                       int argc;
 
                        FOREACH_CLIENT(true, {
                                argc = tokenize_console(strcat("defer_clear ", ftos(etof(it))));
@@ -578,7 +578,7 @@ void GameCommand_defer_clear_all(float request)
        }
 }
 
-void GameCommand_delrec(float request, float argc)  // perhaps merge later with records and printstats and such?
+void GameCommand_delrec(int request, int argc)  // perhaps merge later with records and printstats and such?
 {
        switch (request)
        {
@@ -605,7 +605,7 @@ void GameCommand_delrec(float request, float argc)  // perhaps merge later with
        }
 }
 
-void GameCommand_effectindexdump(float request)
+void GameCommand_effectindexdump(int request)
 {
        switch (request)
        {
@@ -719,7 +719,7 @@ void GameCommand_effectindexdump(float request)
        }
 }
 
-void GameCommand_extendmatchtime(float request)
+void GameCommand_extendmatchtime(int request)
 {
        switch (request)
        {
@@ -740,7 +740,7 @@ void GameCommand_extendmatchtime(float request)
        }
 }
 
-void GameCommand_gametype(float request, float argc)
+void GameCommand_gametype(int request, int argc)
 {
        switch (request)
        {
@@ -790,7 +790,7 @@ void GameCommand_gametype(float request, float argc)
        }
 }
 
-void GameCommand_gettaginfo(float request, float argc)
+void GameCommand_gettaginfo(int request, int argc)
 {
        switch (request)
        {
@@ -855,7 +855,7 @@ void GameCommand_gettaginfo(float request, float argc)
        }
 }
 
-void GameCommand_animbench(float request, float argc)
+void GameCommand_animbench(int request, int argc)
 {
        switch (request)
        {
@@ -914,7 +914,7 @@ void GameCommand_animbench(float request, float argc)
        }
 }
 
-void GameCommand_gotomap(float request, float argc)
+void GameCommand_gotomap(int request, int argc)
 {
        switch (request)
        {
@@ -939,7 +939,7 @@ void GameCommand_gotomap(float request, float argc)
        }
 }
 
-void GameCommand_lockteams(float request)
+void GameCommand_lockteams(int request)
 {
        switch (request)
        {
@@ -968,7 +968,7 @@ void GameCommand_lockteams(float request)
        }
 }
 
-void GameCommand_make_mapinfo(float request)
+void GameCommand_make_mapinfo(int request)
 {
        switch (request)
        {
@@ -994,7 +994,7 @@ void GameCommand_make_mapinfo(float request)
        }
 }
 
-void GameCommand_moveplayer(float request, float argc)
+void GameCommand_moveplayer(int request, int argc)
 {
        switch (request)
        {
@@ -1052,11 +1052,12 @@ void GameCommand_moveplayer(float request, float argc)
                                                        {
                                                                // set up
                                                                float team_id;
-                                                               float save = client.team_forced;
-                                                               client.team_forced = 0;
+                                                               int save = Player_GetForcedTeamIndex(client);
+                                                               Player_SetForcedTeamIndex(client, TEAM_FORCE_DEFAULT);
 
                                                                // find the team to move the player to
                                                                team_id = Team_ColorToTeam(destination);
+                                                               entity balance;
                                                                if (team_id == client.team)  // already on the destination team
                                                                {
                                                                        // keep the forcing undone
@@ -1065,30 +1066,72 @@ void GameCommand_moveplayer(float request, float argc)
                                                                }
                                                                else if (team_id == 0)  // auto team
                                                                {
-                                                                       CheckAllowedTeams(client);
-                                                                       team_id = Team_NumberToTeam(FindSmallestTeam(client, false));
+                                                                       balance = TeamBalance_CheckAllowedTeams(client);
+                                                                       team_id = Team_IndexToTeam(TeamBalance_FindBestTeam(balance, client, false));
                                                                }
                                                                else
                                                                {
-                                                                       CheckAllowedTeams(client);
+                                                                       balance = TeamBalance_CheckAllowedTeams(client);
                                                                }
-                                                               client.team_forced = save;
+                                                               Player_SetForcedTeamIndex(client, save);
 
                                                                // Check to see if the destination team is even available
                                                                switch (team_id)
                                                                {
-                                                                       case NUM_TEAM_1: if (c1 == -1) { LOG_INFO("Sorry, can't move player to red team if it doesn't exist."); return; } break;
-                                                                       case NUM_TEAM_2: if (c2 == -1) { LOG_INFO("Sorry, can't move player to blue team if it doesn't exist."); return; } break;
-                                                                       case NUM_TEAM_3: if (c3 == -1) { LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist."); return; } break;
-                                                                       case NUM_TEAM_4: if (c4 == -1) { LOG_INFO("Sorry, can't move player to pink team if it doesn't exist."); return; } break;
-
-                                                                       default: LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
+                                                                       case NUM_TEAM_1:
+                                                                       {
+                                                                               if (!TeamBalance_IsTeamAllowed(balance, 1))
+                                                                               {
+                                                                                       LOG_INFO("Sorry, can't move player to red team if it doesn't exist.");
+                                                                                       TeamBalance_Destroy(balance);
+                                                                                       return;
+                                                                               }
+                                                                               TeamBalance_Destroy(balance);
+                                                                               break;
+                                                                       }
+                                                                       case NUM_TEAM_2:
+                                                                       {
+                                                                               if (!TeamBalance_IsTeamAllowed(balance, 2))
+                                                                               {
+                                                                                       LOG_INFO("Sorry, can't move player to blue team if it doesn't exist.");
+                                                                                       TeamBalance_Destroy(balance);
+                                                                                       return;
+                                                                               }
+                                                                               TeamBalance_Destroy(balance);
+                                                                               break;
+                                                                       }
+                                                                       case NUM_TEAM_3:
+                                                                       {
+                                                                               if (!TeamBalance_IsTeamAllowed(balance, 3))
+                                                                               {
+                                                                                       LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist.");
+                                                                                       TeamBalance_Destroy(balance);
+                                                                                       return;
+                                                                               }
+                                                                               TeamBalance_Destroy(balance);
+                                                                               break;
+                                                                       }
+                                                                       case NUM_TEAM_4:
+                                                                       {
+                                                                               if (!TeamBalance_IsTeamAllowed(balance, 4))
+                                                                               {
+                                                                                       LOG_INFO("Sorry, can't move player to pink team if it doesn't exist.");
+                                                                                       TeamBalance_Destroy(balance);
+                                                                                       return;
+                                                                               }
+                                                                               TeamBalance_Destroy(balance);
+                                                                               break;
+                                                                       }
+                                                                       default:
+                                                                       {
+                                                                               LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
                                                                                return;
+                                                                       }
                                                                }
 
                                                                // If so, lets continue and finally move the player
-                                                               client.team_forced = 0;
-                                                               if (MoveToTeam(client, team_id, 6))
+                                                               Player_SetForcedTeamIndex(client, TEAM_FORCE_DEFAULT);
+                                                               if (MoveToTeam(client, Team_TeamToIndex(team_id), 6))
                                                                {
                                                                        successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
                                                                        LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
@@ -1136,7 +1179,7 @@ void GameCommand_moveplayer(float request, float argc)
        }
 }
 
-void GameCommand_nospectators(float request)
+void GameCommand_nospectators(int request)
 {
        switch (request)
        {
@@ -1165,7 +1208,7 @@ void GameCommand_nospectators(float request)
        }
 }
 
-void GameCommand_printstats(float request)
+void GameCommand_printstats(int request)
 {
        switch (request)
        {
@@ -1186,7 +1229,7 @@ void GameCommand_printstats(float request)
        }
 }
 
-void GameCommand_radarmap(float request, float argc)
+void GameCommand_radarmap(int request, int argc)
 {
        switch (request)
        {
@@ -1208,7 +1251,7 @@ void GameCommand_radarmap(float request, float argc)
        }
 }
 
-void GameCommand_reducematchtime(float request)
+void GameCommand_reducematchtime(int request)
 {
        switch (request)
        {
@@ -1229,7 +1272,7 @@ void GameCommand_reducematchtime(float request)
        }
 }
 
-void GameCommand_setbots(float request, float argc)
+void GameCommand_setbots(int request, int argc)
 {
        switch (request)
        {
@@ -1256,7 +1299,7 @@ void GameCommand_setbots(float request, float argc)
        }
 }
 
-void GameCommand_shuffleteams(float request)
+void GameCommand_shuffleteams(int request)
 {
        switch (request)
        {
@@ -1269,7 +1312,7 @@ void GameCommand_shuffleteams(float request)
                        }
 
                        FOREACH_CLIENT(IS_PLAYER(it) || it.caplayer, {
-                               if (it.team_forced) {
+                               if (Player_HasRealForcedTeam(it)) {
                                        // we could theoretically assign forced players to their teams
                                        // and shuffle the rest to fill the empty spots but in practise
                                        // either all players or none are gonna have forced teams
@@ -1279,16 +1322,23 @@ void GameCommand_shuffleteams(float request)
                        });
 
                        int number_of_teams = 0;
-                       CheckAllowedTeams(NULL);
-                       if (c1 >= 0) number_of_teams = max(1, number_of_teams);
-                       if (c2 >= 0) number_of_teams = max(2, number_of_teams);
-                       if (c3 >= 0) number_of_teams = max(3, number_of_teams);
-                       if (c4 >= 0) number_of_teams = max(4, number_of_teams);
+                       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+                       for (int i = 1; i <= NUM_TEAMS; ++i)
+                       {
+                               if (TeamBalance_IsTeamAllowed(balance, i))
+                               {
+                                       number_of_teams = max(i, number_of_teams);
+                               }
+                       }
+                       TeamBalance_Destroy(balance);
 
                        int team_index = 0;
                        FOREACH_CLIENT_RANDOM(IS_PLAYER(it) || it.caplayer, {
-                               int target_team_number = Team_NumberToTeam(team_index + 1);
-                               if (it.team != target_team_number) MoveToTeam(it, target_team_number, 6);
+                               int target_team_index = team_index + 1;
+                               if (Entity_GetTeamIndex(it) != target_team_index)
+                               {
+                                       MoveToTeam(it, target_team_index, 6);
+                               }
                                team_index = (team_index + 1) % number_of_teams;
                        });
 
@@ -1307,7 +1357,7 @@ void GameCommand_shuffleteams(float request)
        }
 }
 
-void GameCommand_stuffto(float request, float argc)
+void GameCommand_stuffto(int request, int argc)
 {
        // This... is a fairly dangerous and powerful command... - It allows any arguments to be sent to a client via rcon.
        // Because of this, it is disabled by default and must be enabled by the server owner when doing compilation. That way,
@@ -1356,7 +1406,7 @@ void GameCommand_stuffto(float request, float argc)
 #endif
 }
 
-void GameCommand_trace(float request, float argc)
+void GameCommand_trace(int request, int argc)
 {
        switch (request)
        {
@@ -1532,7 +1582,7 @@ void GameCommand_trace(float request, float argc)
        }
 }
 
-void GameCommand_unlockteams(float request)
+void GameCommand_unlockteams(int request)
 {
        switch (request)
        {
@@ -1561,7 +1611,7 @@ void GameCommand_unlockteams(float request)
        }
 }
 
-void GameCommand_warp(float request, float argc)
+void GameCommand_warp(int request, int argc)
 {
        switch (request)
        {
@@ -1600,7 +1650,7 @@ void GameCommand_warp(float request, float argc)
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void GameCommand_(float request)
+void GameCommand_(int request)
 {
     switch(request)
     {
@@ -1663,7 +1713,7 @@ void GameCommand_macro_help()
        FOREACH(SERVER_COMMANDS, true, { LOG_INFOF("  ^2%s^7: %s", it.m_name, it.m_description); });
 }
 
-float GameCommand_macro_command(float argc, string command)
+float GameCommand_macro_command(int argc, string command)
 {
        string c = strtolower(argv(0));
        FOREACH(SERVER_COMMANDS, it.m_name == c, {
@@ -1673,7 +1723,7 @@ float GameCommand_macro_command(float argc, string command)
        return false;
 }
 
-float GameCommand_macro_usage(float argc)
+float GameCommand_macro_usage(int argc)
 {
        string c = strtolower(argv(1));
        FOREACH(SERVER_COMMANDS, it.m_name == c, {
@@ -1696,7 +1746,7 @@ void GameCommand_macro_write_aliases(float fh)
 
 void GameCommand(string command)
 {
-       float argc = tokenize_console(command);
+       int argc = tokenize_console(command);
 
        // Guide for working with argc arguments by example:
        // argc:   1    - 2      - 3     - 4
index 5f034f12f88534e9f644eeba88bed1351246ecb2..51cf55ce3a1de3ee3c83cc027348ae803f7e072e 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "../g_damage.qh"
 #include "../g_world.qh"
+#include "../teamplay.qh"
 #include "../race.qh"
 #include "../round_handler.qh"
 #include "../scores.qh"
@@ -364,7 +365,7 @@ void reset_map(bool dorespawn)
                if (it.reset2) it.reset2(it);
        });
 
-       FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it); });
+       FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it, false); });
 
        // Moving the player reset code here since the player-reset depends
        // on spawnpoint entities which have to be reset first --blub
@@ -514,7 +515,7 @@ float Votecommand_check_assignment(entity caller, float assignment)
        return false;
 }
 
-string VoteCommand_extractcommand(string input, float startpos, float argc)
+string VoteCommand_extractcommand(string input, float startpos, int argc)
 {
        string output;
 
@@ -571,7 +572,7 @@ string ValidateMap(string validated_map, entity caller)
        return validated_map;
 }
 
-float VoteCommand_checkargs(float startpos, float argc)
+float VoteCommand_checkargs(float startpos, int argc)
 {
        float p, q, check, minargs;
        string cvarname = strcat("sv_vote_command_restriction_", argv(startpos));
@@ -645,7 +646,7 @@ float VoteCommand_checkargs(float startpos, float argc)
        return true;
 }
 
-int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
+int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, int argc)
 {
        string first_command = argv(startpos);
        int missing_chars = argv_start_index(startpos);
@@ -713,6 +714,16 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
                        break;
                }
 
+               case "restart":
+               {
+                       // add a delay so that vote result can be seen and announcer can be heard
+                       // if the vote is accepted
+                       vote_parsed_command = strcat("defer 1 ", vote_command);
+                       vote_parsed_display = strzone(strcat("^1", vote_command));
+
+                       break;
+               }
+
                default:
                {
                        vote_parsed_command = vote_command;
@@ -730,7 +741,7 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
 //  Command Sub-Functions
 // =======================
 
-void VoteCommand_abstain(float request, entity caller)  // CLIENT ONLY
+void VoteCommand_abstain(int request, entity caller)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -762,7 +773,7 @@ void VoteCommand_abstain(float request, entity caller)  // CLIENT ONLY
        }
 }
 
-void VoteCommand_call(float request, entity caller, float argc, string vote_command)  // BOTH
+void VoteCommand_call(int request, entity caller, int argc, string vote_command)  // BOTH
 {
        switch (request)
        {
@@ -824,14 +835,15 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
                                }
 
                                FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
-                               if (tmp_playercount > 1)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
 
                                bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
                                if (autocvar_sv_eventlog)
                                        GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
                                Nagger_VoteChanged();
                                VoteCount(true);  // needed if you are the only one
+
+                               if (tmp_playercount > 1 && vote_called != VOTE_NULL)
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
                        }
 
                        return;
@@ -849,7 +861,7 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
        }
 }
 
-void VoteCommand_master(float request, entity caller, float argc, string vote_command)  // CLIENT ONLY
+void VoteCommand_master(int request, entity caller, int argc, string vote_command)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -964,7 +976,7 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
        }
 }
 
-void VoteCommand_no(float request, entity caller)  // CLIENT ONLY
+void VoteCommand_no(int request, entity caller)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -1002,7 +1014,7 @@ void VoteCommand_no(float request, entity caller)  // CLIENT ONLY
        }
 }
 
-void VoteCommand_status(float request, entity caller)  // BOTH
+void VoteCommand_status(int request, entity caller)  // BOTH
 {
        switch (request)
        {
@@ -1024,7 +1036,7 @@ void VoteCommand_status(float request, entity caller)  // BOTH
        }
 }
 
-void VoteCommand_stop(float request, entity caller)  // BOTH
+void VoteCommand_stop(int request, entity caller)  // BOTH
 {
        switch (request)
        {
@@ -1046,7 +1058,7 @@ void VoteCommand_stop(float request, entity caller)  // BOTH
        }
 }
 
-void VoteCommand_yes(float request, entity caller)  // CLIENT ONLY
+void VoteCommand_yes(int request, entity caller)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -1081,7 +1093,7 @@ void VoteCommand_yes(float request, entity caller)  // CLIENT ONLY
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void VoteCommand_(float request)
+void VoteCommand_(int request)
 {
     switch(request)
     {
@@ -1119,7 +1131,7 @@ void VoteCommand_(float request)
        VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \
        /* nothing */
 
-void VoteCommand_macro_help(entity caller, float argc)
+void VoteCommand_macro_help(entity caller, int argc)
 {
        string command_origin = GetCommandPrefix(caller);
 
@@ -1152,7 +1164,7 @@ void VoteCommand_macro_help(entity caller, float argc)
        }
 }
 
-float VoteCommand_macro_command(entity caller, float argc, string vote_command)
+float VoteCommand_macro_command(entity caller, int argc, string vote_command)
 {
        #define VOTE_COMMAND(name, function, description, assignment) \
                { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } }
@@ -1168,7 +1180,7 @@ float VoteCommand_macro_command(entity caller, float argc, string vote_command)
 //  Main function handling vote commands
 // ======================================
 
-void VoteCommand(float request, entity caller, float argc, string vote_command)
+void VoteCommand(int request, entity caller, int argc, string vote_command)
 {
        // Guide for working with argc arguments by example:
        // argc:   1    - 2      - 3     - 4
index d5c3bc0ad0e937d9569d1ec95cc539cd09c78897..988e91bbce615648873186adf6fb5306d4641c54 100644 (file)
@@ -41,7 +41,7 @@ string vote_parsed_display; // visual string which is fixed after being parsed
 // allow functions to be used in other code like g_world.qc and teamplay.qc
 void VoteThink();
 void VoteReset();
-void VoteCommand(float request, entity caller, float argc, string vote_command);
+void VoteCommand(int request, entity caller, int argc, string vote_command);
 
 // warmup and nagger stuff
 const float RESTART_COUNTDOWN = 10;
index e830fe6c4fe8ed7caf27748935faa8d41cdd4967..c80da0af3772cf40ddd744cf1c471f0268702e23 100644 (file)
@@ -15,4 +15,4 @@ SPAWNFUNC_ITEM(item_spikes, ITEM_Bullets)
 //spawnfunc(item_armor1) {spawnfunc_item_armor_medium(this);}  // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
 SPAWNFUNC_ITEM(item_armor2, ITEM_ArmorMega)
 SPAWNFUNC_ITEM(item_armorInv, ITEM_ArmorMega) // TODO: make sure we actually want this
-spawnfunc(item_health) {if (this.spawnflags & 2) StartItem(this, ITEM_HealthMega);else StartItem(this, ITEM_HealthMedium);}
+SPAWNFUNC_ITEM_COND(item_health, (this.spawnflags & 2), ITEM_HealthMega, ITEM_HealthMedium)
index 4511100581b7d74a89f45e955292f74ce45d04a7..65f231374f56a8053f23a2c2cce49f98df729c15 100644 (file)
@@ -6,6 +6,7 @@
 #include <server/resources.qh>
 #include <common/t_items.qh>
 #include <common/mapobjects/triggers.qh>
+#include <common/mapobjects/trigger/counter.qh>
 #include <common/weapons/_all.qh>
 
 //***********************
@@ -183,6 +184,37 @@ spawnfunc(target_give)
        InitializeEntity(this, target_give_init, INITPRIO_FINDTARGET);
 }
 
+void score_use(entity this, entity actor, entity trigger)
+{
+       if(!IS_PLAYER(actor))
+               return;
+       actor.fragsfilter_cnt += this.count;
+}
+spawnfunc(target_score)
+{
+       if(!g_cts) { delete(this); return; }
+
+       if(!this.count)
+               this.count = 1;
+       this.use = score_use;
+}
+
+void fragsfilter_use(entity this, entity actor, entity trigger)
+{
+       if(!IS_PLAYER(actor))
+               return;
+       if(actor.fragsfilter_cnt >= this.frags)
+               SUB_UseTargets(this, actor, trigger);
+}
+spawnfunc(target_fragsFilter)
+{
+       if(!g_cts) { delete(this); return; }
+
+       if(!this.frags)
+               this.frags = 1;
+       this.use = fragsfilter_use;
+}
+
 //spawnfunc(item_flight)       /* handled by buffs mutator */
 //spawnfunc(item_haste)        /* handled by buffs mutator */
 //spawnfunc(item_health)       /* handled in t_quake.qc */
@@ -233,6 +265,8 @@ bool DoesQ3ARemoveThisEntity(entity this)
                        gametypename = "team";
                if(g_ctf)
                        gametypename = "ctf";
+               if(g_duel)
+                       gametypename = "tournament";
                if(maxclients == 1)
                        gametypename = "single";
                // we do not have the other types (oneflag, obelisk, harvester, teamtournament)
index 342a829a187b12daa84a0b532bc0269aca79c16b..20e4879d9a37596290a2a24258a7d69db623e21a 100644 (file)
@@ -1,3 +1,5 @@
 #pragma once
 
 bool DoesQ3ARemoveThisEntity(entity this);
+
+.int fragsfilter_cnt;
index 4c23aaeb3b956f6706b8dcea57db48f1c4231f01..e667906f1dddb3351a48b6d9c5543fdadcf39f4d 100644 (file)
@@ -25,9 +25,7 @@ float currentbots;
 float bots_would_leave;
 
 void UpdateFrags(entity player, int f);
-.float totalfrags;
-
-float team1_score, team2_score, team3_score, team4_score;
+.int totalfrags;
 
 // flag set on worldspawn so that the code knows if it is dedicated or not
 float server_is_dedicated;
@@ -43,7 +41,7 @@ float server_is_dedicated;
 
 //.float       worldtype;
 // Needed for dynamic clientwalls
-.float inactive; // Clientwall disappears when inactive
+.bool inactive; // Clientwall disappears when inactive
 .float alpha_max, alpha_min;
 .float fade_start, fade_end, fade_vertical_offset;
 .float default_solid; // Variable to store default .solid for clientwalls
@@ -170,6 +168,7 @@ float default_weapon_alpha;
 .float cvar_cl_jetpack_jump;
 .float cvar_cl_movement_track_canjump;
 .float cvar_cl_newusekeysupported;
+.float cvar_cl_cts_noautoswitch;
 
 .string cvar_g_xonoticversion;
 .string cvar_cl_weaponpriority;
@@ -203,10 +202,7 @@ float bot_waypoints_for_items;
 #else
 #define ATTACK_FINISHED_FOR(ent, w, slot) ((ent).attack_finished_single[slot])
 #endif
-#define ATTACK_FINISHED(ent, slot) ATTACK_FINISHED_FOR(ent, ent.(weaponentity).m_weapon.m_id, slot)
-
-// assault game mode: Which team is attacking in this round?
-float assault_attacker_team;
+#define ATTACK_FINISHED(ent, w) ATTACK_FINISHED_FOR(ent, ent.(w).m_weapon.m_id, weaponslot(w))
 
 // speedrun: when 1, player auto teleports back when capture timeout happens
 .float speedrunning;
@@ -215,15 +211,12 @@ float assault_attacker_team;
 float ServerProgsDB;
 float TemporaryDB;
 
-.float team_saved;
+.int team_saved;
 
 bool some_spawn_has_been_used;
 int have_team_spawns; // 0 = no team spawns requested, -1 = team spawns requested but none found, 1 = team spawns requested and found
 int have_team_spawns_forteams; // if Xth bit is 1 then team X has spawns else it has no spawns; team 0 is the "no-team"
 
-// set when showing a kill countdown
-.entity killindicator;
-
 .bool canteamdamage;
 
 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
@@ -231,8 +224,6 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
 // WEAPONTODO
 #define DMG_NOWEP (weaponentities[0])
 
-float lockteams;
-
 float sv_maxidle;
 float sv_maxidle_spectatorsareidle;
 int sv_maxidle_slots;
@@ -312,9 +303,9 @@ float client_cefc_accumulatortime;
 
 .float weapon_load[Weapons_MAX];
 .int ammo_none; // used by the reloading system, must always be 0
-.float clip_load;
-.float old_clip_load;
-.float clip_size;
+.int clip_load;
+.int old_clip_load;
+.int clip_size;
 
 .int minelayer_mines;
 .float vortex_charge;
@@ -331,9 +322,9 @@ float client_cefc_accumulatortime;
 // when doing this, hagar can go through clones
 // #define PROJECTILE_MAKETRIGGER(e) (e).solid = SOLID_BBOX
 
-.float spectatee_status;
-.float zoomstate;
-.float restriction;
+.int spectatee_status;
+.bool zoomstate;
+.int restriction;
 
 .entity clientdata;
 .entity personal;
@@ -343,24 +334,27 @@ string deathmessage;
 .bool just_joined;
 
 .float cvar_cl_weaponimpulsemode;
-.float selectweapon; // last selected weapon of the player
+.int selectweapon; // last selected weapon of the player
 
 .float ballistics_density; // wall piercing factor, larger = bullet can pass through more
 
-const float ACTIVE_NOT                 = 0;
-const float ACTIVE_ACTIVE      = 1;
-const float ACTIVE_IDLE        = 2;
-const float ACTIVE_BUSY        = 2;
-const float ACTIVE_TOGGLE      = 3;
-.float active;
+//const int FROZEN_NOT                         = 0;
+const int FROZEN_NORMAL                                = 1;
+const int FROZEN_TEMP_REVIVING         = 2;
+const int FROZEN_TEMP_DYING                    = 3;
+
+const int ACTIVE_NOT           = 0;
+const int ACTIVE_ACTIVE        = 1;
+const int ACTIVE_IDLE          = 2;
+const int ACTIVE_BUSY          = 2;
+const int ACTIVE_TOGGLE        = 3;
+.int active;
 .void (entity this, int act_state) setactive;
 .entity realowner;
 
 //float serverflags;
 
-.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
-
-.float player_blocked;
+.bool player_blocked;
 
 .float revival_time; // time at which player was last revived
 .float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
@@ -371,8 +365,6 @@ const float ACTIVE_TOGGLE   = 3;
 .entity muzzle_flash;
 .float misc_bulletcounter;     // replaces uzi & hlac bullet counter.
 
-.int killindicator_teamchange;
-
 void PlayerUseKey(entity this);
 
 USING(spawn_evalfunc_t, vector(entity this, entity player, entity spot, vector current));
@@ -380,7 +372,7 @@ USING(spawn_evalfunc_t, vector(entity this, entity player, entity spot, vector c
 
 string modname;
 
-.float missile_flags;
+.int missile_flags;
 const int MIF_SPLASH = BIT(1);
 const int MIF_ARC = BIT(2);
 const int MIF_PROXY = BIT(3);
index a25ae5bec9f878d9ba5feca5a6bb2f7e7c55bae6..e9ab3d79ac945a2fa9cec1cf2a392b16aa72fbe0 100644 (file)
@@ -4,6 +4,7 @@
 #include "bot/api.qh"
 #include "g_hook.qh"
 #include <server/mutators/_mod.qh>
+#include "teamplay.qh"
 #include "scores.qh"
 #include "spawnpoints.qh"
 #include "../common/state.qh"
@@ -488,8 +489,8 @@ void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
        float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
 
        STAT(FROZEN, targ) = frozen_type;
-       STAT(REVIVE_PROGRESS, targ) = ((frozen_type == 3) ? 1 : 0);
-       SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
+       STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
+       SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
        targ.revive_speed = revivespeed;
        if(targ.bot_attack)
                IL_REMOVE(g_bot_targets, targ);
@@ -528,16 +529,15 @@ void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
                WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
 }
 
-void Unfreeze(entity targ)
+void Unfreeze(entity targ, bool reset_health)
 {
        if(!STAT(FROZEN, targ))
                return;
 
-       if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
-       {
+       if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
                SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
-               targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
-       }
+
+       targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
 
        STAT(FROZEN, targ) = 0;
        STAT(REVIVE_PROGRESS, targ) = 0;
@@ -696,7 +696,7 @@ void Damage(entity targ, entity inflictor, entity attacker, float damage, int de
                        if(deathtype == DEATH_FALL.m_id)
                        if(damage >= autocvar_g_frozen_revive_falldamage)
                        {
-                               Unfreeze(targ);
+                               Unfreeze(targ, false);
                                SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
                                Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
                                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
@@ -1053,7 +1053,7 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in
        RadiusDamage_running = 0;
 
        if(!DEATH_ISSPECIAL(deathtype))
-               accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
+               accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
 
        return total_damage_to_creatures;
 }
@@ -1182,7 +1182,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
                                }
                        }
                        if(accuracy_isgooddamage(o, e))
-                               accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
+                               accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
                        return max(0, totaldamage - mindamage); // can never be negative, but to make sure
                }
                else
@@ -1196,7 +1196,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
                e.fire_owner = o;
                e.fire_hitsound = false;
                if(accuracy_isgooddamage(o, e))
-                       accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
+                       accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
                return d;
        }
 }
@@ -1235,11 +1235,11 @@ void Fire_ApplyDamage(entity e)
        }
        e.fire_hitsound = true;
 
-       if(!IS_INDEPENDENT_PLAYER(e))
-       if(!STAT(FROZEN, e))
-               FOREACH_CLIENT(IS_PLAYER(it) && it != e, {
-                       if(!IS_DEAD(it))
-                       if(!IS_INDEPENDENT_PLAYER(it))
+       if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
+       {
+               IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
+               {
+                       if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
                        if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
                        {
                                t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
@@ -1247,6 +1247,7 @@ void Fire_ApplyDamage(entity e)
                                Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
                        }
                });
+       }
 }
 
 void Fire_ApplyEffect(entity e)
index 0af110c9e890f29925db0803eedcd8d29f4eba04..1d100e7a5a86874b2be4ed55771584f708581fd0 100644 (file)
@@ -49,7 +49,7 @@ float damage_gooddamage;
 .float teamkill_soundtime;
 .entity teamkill_soundsource;
 .entity pusher;
-.float istypefrag;
+.bool istypefrag;
 .float taunt_soundtime;
 
 float IsFlying(entity a);
@@ -85,7 +85,7 @@ void Ice_Think(entity this);
 
 void Freeze(entity targ, float freeze_time, int frozen_type, bool show_waypoint);
 
-void Unfreeze (entity targ);
+void Unfreeze(entity targ, bool reset_health);
 
 // NOTE: the .weaponentity parameter can be set to DMG_NOWEP if the attack wasn't caused by a weapon or player
 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
index 26a4cfb08685c3d16f58f8acd5416d693ed65b18..522f4f041cc49af4810c0f53fb2070f30c4e75e4 100644 (file)
@@ -16,6 +16,7 @@
 #include <server/mutators/_mod.qh>
 #include "race.qh"
 #include "scores.qh"
+#include "scores_rules.qh"
 #include "teamplay.qh"
 #include "weapons/weaponstats.qh"
 #include "../common/constants.qh"
@@ -264,6 +265,7 @@ void cvar_changes_init()
                BADCVAR("g_dm");
                BADCVAR("g_domination");
                BADCVAR("g_domination_default_teams");
+               BADCVAR("g_duel");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
                BADCVAR("g_invasion_teams");
@@ -578,6 +580,58 @@ STATIC_INIT_EARLY(maxclients)
        }
 }
 
+void default_delayedinit(entity this)
+{
+       if(!scores_initialized)
+               ScoreRules_generic();
+}
+
+void InitGameplayMode()
+{
+       VoteReset();
+
+       // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
+       get_mi_min_max(1);
+       // assign reflectively to avoid "assignment to world" warning
+       int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
+           string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
+           if (v) {
+            putentityfieldstring(i, world, sprintf("%v", v));
+            if (++done == 2) break;
+        }
+       }
+       // currently, NetRadiant's limit is 131072 qu for each side
+       // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
+       // set the distance according to map size but don't go over the limit to avoid issues with float precision
+       // in case somebody makes extremely large maps
+       max_shot_distance = min(230000, vlen(world.maxs - world.mins));
+
+       MapInfo_LoadMapSettings(mapname);
+       GameRules_teams(false);
+
+       if (!cvar_value_issafe(world.fog))
+       {
+               LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
+               world.fog = string_null;
+       }
+       if(MapInfo_Map_fog != "")
+               if(MapInfo_Map_fog == "none")
+                       world.fog = string_null;
+               else
+                       world.fog = strzone(MapInfo_Map_fog);
+       clientstuff = strzone(MapInfo_Map_clientstuff);
+
+       MapInfo_ClearTemps();
+
+       gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
+
+       cache_mutatormsg = strzone("");
+       cache_lastmutatormsg = strzone("");
+
+       InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
+}
+
+void Map_MarkAsRecent(string m);
 float world_already_spawned;
 spawnfunc(worldspawn)
 {
@@ -1558,7 +1612,9 @@ float InitiateSuddenDeath()
        // - for this timelimit_overtime needs to be >0 of course
        // - also check the winning condition calculated in the previous frame and only add normal overtime
        //   again, if at the point at which timelimit would be extended again, still no winner was found
-       if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
+       if (!autocvar_g_campaign && checkrules_overtimesadded >= 0
+               && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0)
+               && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
        {
                return 1; // need to call InitiateOvertime later
        }
@@ -1581,11 +1637,7 @@ void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
 {
        ++checkrules_overtimesadded;
        //add one more overtime by simply extending the timelimit
-       float tl;
-       tl = autocvar_timelimit;
-       tl += autocvar_timelimit_overtime;
-       cvar_set("timelimit", ftos(tl));
-
+       cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
 }
 
@@ -1646,10 +1698,11 @@ float WinningCondition_Scores(float limit, float leadlimit)
 
        if(teamplay)
        {
-               team1_score = TeamScore_GetCompareValue(NUM_TEAM_1);
-               team2_score = TeamScore_GetCompareValue(NUM_TEAM_2);
-               team3_score = TeamScore_GetCompareValue(NUM_TEAM_3);
-               team4_score = TeamScore_GetCompareValue(NUM_TEAM_4);
+               for (int i = 1; i < 5; ++i)
+               {
+                       Team_SetTeamScore(Team_GetTeamFromIndex(i),
+                               TeamScore_GetCompareValue(Team_IndexToTeam(i)));
+               }
        }
 
        ClearWinners();
@@ -1719,30 +1772,32 @@ float WinningCondition_RanOutOfSpawns()
        if(!some_spawn_has_been_used)
                return WINNING_NO;
 
-       team1_score = team2_score = team3_score = team4_score = 0;
+       for (int i = 1; i < 5; ++i)
+       {
+               Team_SetTeamScore(Team_GetTeamFromIndex(i), 0);
+       }
 
-       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
-               switch(it.team)
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if (Team_IsValidTeam(it.team))
                {
-                       case NUM_TEAM_1: team1_score = 1; break;
-                       case NUM_TEAM_2: team2_score = 1; break;
-                       case NUM_TEAM_3: team3_score = 1; break;
-                       case NUM_TEAM_4: team4_score = 1; break;
+                       Team_SetTeamScore(Team_GetTeam(it.team), 1);
                }
        });
 
        IL_EACH(g_spawnpoints, true,
        {
-               switch(it.team)
+               if (Team_IsValidTeam(it.team))
                {
-                       case NUM_TEAM_1: team1_score = 1; break;
-                       case NUM_TEAM_2: team2_score = 1; break;
-                       case NUM_TEAM_3: team3_score = 1; break;
-                       case NUM_TEAM_4: team4_score = 1; break;
+                       Team_SetTeamScore(Team_GetTeam(it.team), 1);
                }
        });
 
        ClearWinners();
+       float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1));
+       float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2));
+       float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3));
+       float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4));
        if(team1_score + team2_score + team3_score + team4_score == 0)
        {
                checkrules_equality = true;
@@ -1752,20 +1807,28 @@ float WinningCondition_RanOutOfSpawns()
        {
                float t, i;
                if(team1_score)
-                       t = NUM_TEAM_1;
+                       t = 1;
                else if(team2_score)
-                       t = NUM_TEAM_2;
+                       t = 2;
                else if(team3_score)
-                       t = NUM_TEAM_3;
+                       t = 3;
                else // if(team4_score)
-                       t = NUM_TEAM_4;
-               CheckAllowedTeams(NULL);
+                       t = 4;
+               entity balance = TeamBalance_CheckAllowedTeams(NULL);
                for(i = 0; i < MAX_TEAMSCORE; ++i)
                {
-                       if(t != NUM_TEAM_1) if(c1 >= 0) TeamScore_AddToTeam(NUM_TEAM_1, i, -1000);
-                       if(t != NUM_TEAM_2) if(c2 >= 0) TeamScore_AddToTeam(NUM_TEAM_2, i, -1000);
-                       if(t != NUM_TEAM_3) if(c3 >= 0) TeamScore_AddToTeam(NUM_TEAM_3, i, -1000);
-                       if(t != NUM_TEAM_4) if(c4 >= 0) TeamScore_AddToTeam(NUM_TEAM_4, i, -1000);
+                       for (int j = 1; j <= NUM_TEAMS; ++j)
+                       {
+                               if (t == j)
+                               {
+                                       continue;
+                               }
+                               if (!TeamBalance_IsTeamAllowed(balance, j))
+                               {
+                                       continue;
+                               }
+                               TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000);
+                       }
                }
 
                AddWinners(team, t);
index c0c35589a865f476ea08d006f5f43c2ab7996ac6..da950f18575e28a410fbed9955b90a0a5ced4ed7 100644 (file)
@@ -5,6 +5,9 @@ float checkrules_suddendeathwarning;
 float checkrules_suddendeathend;
 float checkrules_overtimesadded; //how many overtimes have been already added
 
+string cache_mutatormsg;
+string cache_lastmutatormsg;
+
 const int WINNING_NO = 0; // no winner, but time limits may terminate the game
 const int WINNING_YES = 1; // winner found
 const int WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached
index 5010d283711ff0e795ed3f146bb097b56cfbcd10..8a17ef6a1dd44abfca2b064b20434285a5df1054 100644 (file)
@@ -1,11 +1,10 @@
 #include "impulse.qh"
 #include "round_handler.qh"
 
-#include "bot/api.qh"
-
 #include "weapons/throwing.qh"
 #include "command/common.qh"
 #include "cheats.qh"
+#include "clientkill.qh"
 #include "weapons/selection.qh"
 #include "weapons/tracing.qh"
 #include "weapons/weaponsystem.qh"
@@ -150,7 +149,7 @@ X(9, next)
                for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) \
                { \
                        .entity weaponentity = weaponentities[slot]; \
-                       W_SwitchWeapon(this, Weapons_from(WEP_FIRST + i), weaponentity); \
+                       W_SwitchWeapon_TryOthers(this, Weapons_from(WEP_FIRST + i), weaponentity); \
                        if(slot == 0 && autocvar_g_weaponswitch_debug != 1) \
                                break; \
                } \
@@ -571,33 +570,3 @@ IMPULSE(waypoint_clear)
        }
        sprint(this, "all waypoints cleared\n");
 }
-
-IMPULSE(navwaypoint_spawn)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_spawn_fromeditor(this);
-}
-
-IMPULSE(navwaypoint_remove)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_remove_fromeditor(this);
-}
-
-IMPULSE(navwaypoint_relink)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_schedulerelinkall();
-}
-
-IMPULSE(navwaypoint_save)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_saveall();
-}
-
-IMPULSE(navwaypoint_unreachable)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_unreachable(this);
-}
index 2c0af1c8f2ac12ab4d214bfc84e51dbc94388158..7c6fcbafef635c9aff2bddca7ab30ec8c23d45de 100644 (file)
@@ -197,7 +197,7 @@ LABEL(skip)
 
 void OnlineBanList_Think(entity this)
 {
-       float argc;
+       int argc;
        string uri;
        float i, n;
 
@@ -446,6 +446,8 @@ bool Ban_MaybeEnforceBan(entity client)
        if (Ban_IsClientBanned(client, -1))
        {
                string s = sprintf("^1NOTE:^7 banned client %s just tried to enter\n", client.netaddress);
+               if(autocvar_g_ban_telluser)
+                       sprint(client, "You are banned from this server.\n");
                dropclient(client);
                bprint(s);
                return true;
diff --git a/qcsrc/server/item_key.qc b/qcsrc/server/item_key.qc
deleted file mode 100644 (file)
index 2bfdc49..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-#include "item_key.qh"
-
-#include "../common/mapobjects/subs.qh"
-#include <common/mapobjects/triggers.qh>
-#include "../common/monsters/_mod.qh"
-#include "../common/notifications/all.qh"
-#include "../common/util.qh"
-#include "../lib/warpzone/util_server.qh"
-
-/*
-TODO:
-- add an unlock sound (here to trigger_keylock and to func_door)
-- display available keys on the HUD
-- make more tests
-- think about adding NOT_EASY/NOT_NORMAL/NOT_HARD for Q1 compatibility
-- should keys have a trigger?
-*/
-
-bool item_keys_usekey(entity l, entity p)
-{
-       int valid = l.itemkeys & PS(p).itemkeys;
-
-       if (!valid) {
-               // player has none of the needed keys
-               return false;
-       } else if (l.itemkeys == valid) {
-               // ALL needed keys were given
-               l.itemkeys = 0;
-               return true;
-       } else {
-               // only some of the needed keys were given
-               l.itemkeys &= ~valid;
-               return true;
-       }
-}
-
-string item_keys_keylist(float keylist) {
-       // no keys
-       if (!keylist)
-               return "";
-
-       // one key
-       if ((keylist & (keylist-1)) == 0)
-               return strcat("the ", item_keys_names[lowestbit(keylist)]);
-
-       string n = "";
-       int base = 0;
-       while (keylist) {
-               int l = lowestbit(keylist);
-               if (n)
-                       n = strcat(n, ", the ", item_keys_names[base + l]);
-               else
-                       n = strcat("the ", item_keys_names[base + l]);
-
-               keylist = bitshift(keylist,  -(l + 1));
-               base+= l + 1;
-       }
-
-       return n;
-}
-
-
-/*
-================================
-item_key
-================================
-*/
-
-/**
- * Key touch handler.
- */
-void item_key_touch(entity this, entity toucher)
-{
-       if (!IS_PLAYER(toucher))
-               return;
-
-       // player already picked up this key
-       if (PS(toucher).itemkeys & this.itemkeys)
-               return;
-
-       PS(toucher).itemkeys |= this.itemkeys;
-       play2(toucher, this.noise);
-
-       centerprint(toucher, this.message);
-
-       string oldmsg = this.message;
-       this.message = "";
-       SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
-       this.message = oldmsg;
-}
-
-/**
- * Spawn a key with given model, key code and color.
- */
-void spawn_item_key(entity this)
-{
-       precache_model(this.model);
-
-       if (this.spawnflags & 1) // FLOATING
-               this.noalign = 1;
-
-       if (this.noalign)
-               set_movetype(this, MOVETYPE_NONE);
-       else
-               set_movetype(this, MOVETYPE_TOSS);
-
-       precache_sound(this.noise);
-
-       this.mdl = this.model;
-       this.effects = EF_LOWPRECISION;
-       _setmodel(this, this.model);
-       //setsize(this, '-16 -16 -24', '16 16 32');
-       setorigin(this, this.origin + '0 0 32');
-       setsize(this, '-16 -16 -56', '16 16 0');
-       this.modelflags |= MF_ROTATE;
-       this.solid = SOLID_TRIGGER;
-
-       if (!this.noalign)
-       {
-               // first nudge it off the floor a little bit to avoid math errors
-               setorigin(this, this.origin + '0 0 1');
-               // note droptofloor returns false if stuck/or would fall too far
-               droptofloor(this);
-       }
-
-       settouch(this, item_key_touch);
-}
-
-
-/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
-A key entity.
-The itemkeys should contain one of the following key IDs:
-1 - GOLD key -
-2 - SILVER key
-4 - BRONZE key
-8 - RED keycard
-16 - BLUE keycard
-32 - GREEN keycard
-Custom keys:
-... - last key is 1<<23
-Keys with bigger Id than 32 don't have a default netname and model, if you use one of them, you MUST provide those.
------------KEYS------------
-colormod: color of the key (default: '.9 .9 .9').
-itemkeys: a key Id.
-message: message to print when player picks up this key.
-model: custom key model to use.
-netname: the display name of the key.
-noise: custom sound to play when player picks up the key.
--------- SPAWNFLAGS --------
-FLOATING: the item will float in air, instead of aligning to the floor by falling
----------NOTES----------
-This is the only correct way to put keys on the map!
-
-itemkeys MUST always have exactly one bit set.
-*/
-spawnfunc(item_key)
-{
-       string _netname;
-       vector _colormod;
-
-       // reject this entity if more than one key was set!
-       if (this.itemkeys>0 && (this.itemkeys & (this.itemkeys-1)) != 0) {
-               objerror(this, "item_key.itemkeys must contain only 1 bit set specifying the key it represents!");
-               delete(this);
-               return;
-       }
-
-       // find default netname and colormod
-       switch(this.itemkeys) {
-       case BIT(0):
-               _netname = "GOLD key";
-               _colormod = '1 .9 0';
-               break;
-
-       case BIT(1):
-               _netname = "SILVER key";
-               _colormod = '.9 .9 .9';
-               break;
-
-       case BIT(2):
-               _netname = "BRONZE key";
-               _colormod = '.6 .25 0';
-               break;
-
-       case BIT(3):
-               _netname = "RED keycard";
-               _colormod = '.9 0 0';
-               break;
-
-       case BIT(4):
-               _netname = "BLUE keycard";
-               _colormod = '0 0 .9';
-               break;
-
-       case BIT(5):
-               _netname = "GREEN keycard";
-               _colormod = '0 .9 0';
-               break;
-
-       default:
-               _netname = "FLUFFY PINK keycard";
-               _colormod = '1 1 1';
-
-               if (this.netname == "") {
-                       objerror(this, "item_key doesn't have a default name for this key and a custom one was not specified!");
-                       delete(this);
-                       return;
-               }
-               break;
-
-       }
-
-       // find default model
-       string _model = string_null;
-       if (this.itemkeys <= ITEM_KEY_BIT(2)) {
-               _model = "models/keys/key.md3";
-       } else if (this.itemkeys >= ITEM_KEY_BIT(3) && this.itemkeys <= ITEM_KEY_BIT(5)) {
-               _model = "models/keys/key.md3"; // FIXME: replace it by a keycard model!
-       } else if (this.model == "") {
-               objerror(this, "item_key doesn't have a default model for this key and a custom one was not specified!");
-               delete(this);
-               return;
-       }
-
-       // set defailt netname
-       if (this.netname == "")
-               this.netname = _netname;
-
-       // set default colormod
-       if (!this.colormod)
-               this.colormod = _colormod;
-
-       // set default model
-       if (this.model == "")
-               this.model = _model;
-
-       // set default pickup message
-       if (this.message == "")
-               this.message = strzone(strcat("You've picked up the ", this.netname, "!"));
-
-       if (this.noise == "")
-               this.noise = strzone(SND(ITEMPICKUP));
-
-       // save the name for later
-       item_keys_names[lowestbit(this.itemkeys)] = this.netname;
-
-       // put the key on the map
-       spawn_item_key(this);
-}
-
-/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
-SILVER key.
------------KEYS------------
-colormod: color of the key (default: '.9 .9 .9').
-message: message to print when player picks up this key.
-model: custom model to use.
-noise: custom sound to play when player picks up the key.
--------- SPAWNFLAGS --------
-FLOATING: the item will float in air, instead of aligning to the floor by falling
----------NOTES----------
-Don't use this entity on new maps! Use item_key instead.
-*/
-spawnfunc(item_key1)
-{
-       this.classname = "item_key";
-       this.itemkeys = ITEM_KEY_BIT(1);
-       spawnfunc_item_key(this);
-}
-
-/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
-GOLD key.
------------KEYS------------
-colormod: color of the key (default: '1 .9 0').
-message: message to print when player picks up this key.
-model: custom model to use.
-noise: custom sound to play when player picks up the key.
--------- SPAWNFLAGS --------
-FLOATING: the item will float in air, instead of aligning to the floor by falling
----------NOTES----------
-Don't use this entity on new maps! Use item_key instead.
-*/
-spawnfunc(item_key2)
-{
-       this.classname = "item_key";
-       this.itemkeys = ITEM_KEY_BIT(0);
-       spawnfunc_item_key(this);
-}
diff --git a/qcsrc/server/item_key.qh b/qcsrc/server/item_key.qh
deleted file mode 100644 (file)
index 50be5f8..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-/**
- * Returns the bit ID of a key
- */
-#define ITEM_KEY_BIT(n)        ( bitshift(1, n) )
-
-#define ITEM_KEY_MAX   24
-
-/**
- * list of key names.
- */
-#ifdef SVQC
-string item_keys_names[ITEM_KEY_MAX];
-
-/**
- * Use keys from p on l.
- * Returns true if any new keys were given, false otherwise.
- */
-float item_keys_usekey(entity l, entity p);
-
-/**
- * Returns a string with a comma separated list of key names, as specified in keylist.
- */
-string item_keys_keylist(float keylist);
-#endif
index 4d235da694539179a69b46a67bf4e87dd0d42ee2..b495b4b0d8e8ab3b8cab12800380a615b2750559 100644 (file)
@@ -1,6 +1,6 @@
 #include "matrix.qh"
 
-#include "player.qh"
+#include "client.qh"
 
 var void MX_Handle(int buf, string ancestor)
 {
index 319b6f16ff7e3c7c333d8fb3a67e2b360eefbde8..47c0cb2fe4615af49105c651d2bc3192c9452b5f 100644 (file)
@@ -40,29 +40,38 @@ void crosshair_trace(entity pl)
 {
        traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
 }
-.bool ctrace_solidchanged;
+
 void crosshair_trace_plusvisibletriggers(entity pl)
+{
+       crosshair_trace_plusvisibletriggers__is_wz(pl, false);
+}
+
+void WarpZone_crosshair_trace_plusvisibletriggers(entity pl)
+{
+       crosshair_trace_plusvisibletriggers__is_wz(pl, true);
+}
+
+void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz)
 {
        FOREACH_ENTITY_FLOAT(solid, SOLID_TRIGGER,
        {
                if(it.model != "")
                {
                        it.solid = SOLID_BSP;
-                       it.ctrace_solidchanged = true;
                        IL_PUSH(g_ctrace_changed, it);
                }
        });
 
-       crosshair_trace(pl);
+       if (is_wz)
+               WarpZone_crosshair_trace(pl);
+       else
+               crosshair_trace(pl);
 
-       IL_EACH(g_ctrace_changed, it.ctrace_solidchanged,
-       {
-               it.solid = SOLID_TRIGGER;
-               it.ctrace_solidchanged = false;
-       });
+       IL_EACH(g_ctrace_changed, true, { it.solid = SOLID_TRIGGER; });
 
        IL_CLEAR(g_ctrace_changed);
 }
+
 void WarpZone_crosshair_trace(entity pl)
 {
        WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
@@ -223,16 +232,13 @@ string formatmessage(entity this, string msg)
 {
        float p, p1, p2;
        float n;
-       vector cursor;
-       entity cursor_ent;
+       vector cursor = '0 0 0';
+       entity cursor_ent = NULL;
        string escape;
        string replacement;
        p = 0;
        n = 7;
-
-       WarpZone_crosshair_trace(this);
-       cursor = trace_endpos;
-       cursor_ent = trace_ent;
+       bool traced = false;
 
        MUTATOR_CALLHOOK(PreFormatMessage, this, msg);
        msg = M_ARGV(1, string);
@@ -256,6 +262,14 @@ string formatmessage(entity this, string msg)
                if (p < 0)
                        break;
 
+               if(!traced)
+               {
+                       WarpZone_crosshair_trace_plusvisibletriggers(this);
+                       cursor = trace_endpos;
+                       cursor_ent = trace_ent;
+                       traced = true;
+               }
+
                replacement = substring(msg, p, 2);
                escape = substring(msg, p + 1, 1);
 
@@ -402,6 +416,8 @@ REPLICATE(cvar_cl_weaponimpulsemode, int, "cl_weaponimpulsemode");
 
 REPLICATE(cvar_g_xonoticversion, string, "g_xonoticversion");
 
+REPLICATE(cvar_cl_cts_noautoswitch, bool, "cl_cts_noautoswitch");
+
 /**
  * @param f -1: cleanup, 0: request, 1: receive
  */
@@ -586,14 +602,12 @@ void readplayerstartcvars()
                for (i = 0; i < t; ++i)
                {
                        s = argv(i);
-                       FOREACH(Weapons, it != WEP_Null, {
-                               if(it.netname == s)
-                               {
-                                       g_weaponarena_weapons |= (it.m_wepset);
-                                       g_weaponarena_list = strcat(g_weaponarena_list, it.m_name, " & ");
-                                       break;
-                               }
-                       });
+                       Weapon wep = Weapons_fromstr(s);
+                       if(wep != WEP_Null)
+                       {
+                               g_weaponarena_weapons |= (wep.m_wepset);
+                               g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
+                       }
                }
                g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
        }
@@ -618,6 +632,9 @@ void readplayerstartcvars()
                });
        }
 
+       if(cvar("g_balance_superweapons_time") < 0)
+               start_items |= IT_UNLIMITED_SUPERWEAPONS;
+
        if(!cvar("g_use_ammunition"))
                start_items |= IT_UNLIMITED_AMMO;
 
@@ -875,7 +892,7 @@ void remove_safely(entity e)
     builtin_remove(e);
 }
 
-void InitializeEntity(entity e, void(entity this) func, float order)
+void InitializeEntity(entity e, void(entity this) func, int order)
 {
     entity prev, cur;
 
index 1455054d2c5cad597ec87e84412d45b74ea6dc00..2374b4869bc6c5415c08c319a162850ca9871440 100644 (file)
@@ -62,6 +62,8 @@ void attach_sameorigin(entity e, entity to, string tag);
 void crosshair_trace(entity pl);
 
 void crosshair_trace_plusvisibletriggers(entity pl);
+void WarpZone_crosshair_trace_plusvisibletriggers(entity pl);
+void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz);
 
 void detach_sameorigin(entity e);
 
@@ -94,6 +96,8 @@ float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, f
 
 string NearestLocation(vector p);
 
+string AmmoNameFromWeaponentity(Weapon wep);
+
 void play2(entity e, string filename);
 
 string playername(entity p, bool team_colorize);
@@ -324,17 +328,17 @@ void readlevelcvars()
 
 //#NO AUTOCVARS END
 
-const float INITPRIO_FIRST                             = 0;
-const float INITPRIO_GAMETYPE                  = 0;
-const float INITPRIO_GAMETYPE_FALLBACK         = 1;
-const float INITPRIO_FINDTARGET                = 10;
-const float INITPRIO_DROPTOFLOOR               = 20;
-const float INITPRIO_SETLOCATION               = 90;
-const float INITPRIO_LINKDOORS                         = 91;
-const float INITPRIO_LAST                              = 99;
+const int INITPRIO_FIRST               = 0;
+const int INITPRIO_GAMETYPE            = 0;
+const int INITPRIO_GAMETYPE_FALLBACK   = 1;
+const int INITPRIO_FINDTARGET          = 10;
+const int INITPRIO_DROPTOFLOOR                 = 20;
+const int INITPRIO_SETLOCATION                 = 90;
+const int INITPRIO_LINKDOORS           = 91;
+const int INITPRIO_LAST                = 99;
 
 .void(entity this) initialize_entity;
-.float initialize_entity_order;
+.int initialize_entity_order;
 .entity initialize_entity_next;
 entity initialize_entity_first;
 
@@ -342,8 +346,8 @@ entity initialize_entity_first;
 
 
 
-float sound_allowed(float dest, entity e);
-void InitializeEntity(entity e, void(entity this) func, float order);
+bool sound_allowed(int dest, entity e);
+void InitializeEntity(entity e, void(entity this) func, int order);
 
 IntrusiveList g_ctrace_changed;
 STATIC_INIT(g_ctrace_changed) { g_ctrace_changed = IL_NEW(); }
index a4cf9df8ef30cc5e6554f4403112a28723a07e84..864623bbae4b888f5c3ca4e56e40d70b0d513246 100644 (file)
@@ -137,40 +137,51 @@ MUTATOR_HOOKABLE(GiveFragsForKill, EV_GiveFragsForKill);
 /** called when the match ends */
 MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
 
-/** allows adjusting allowed teams */
-#define EV_CheckAllowedTeams(i, o) \
+/** Allows adjusting allowed teams. Return true to use the bitmask value and set
+ * non-empty string to use team entity name. Both behaviors can be active at the
+ * same time and will stack allowed teams.
+ */
+#define EV_TeamBalance_CheckAllowedTeams(i, o) \
     /** mask of teams      */ i(float, MUTATOR_ARGV_0_float) \
     /**/                      o(float, MUTATOR_ARGV_0_float) \
     /** team entity name   */ i(string, MUTATOR_ARGV_1_string) \
     /**/                      o(string, MUTATOR_ARGV_1_string) \
     /** player checked     */ i(entity, MUTATOR_ARGV_2_entity) \
     /**/
-MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams);
+MUTATOR_HOOKABLE(TeamBalance_CheckAllowedTeams,
+       EV_TeamBalance_CheckAllowedTeams);
 
 /** return true to manually override team counts */
-MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS);
+MUTATOR_HOOKABLE(TeamBalance_GetTeamCounts, EV_NO_ARGS);
 
-/** allow overriding of team counts */
-#define EV_GetTeamCount(i, o) \
-    /** team to count                   */ i(float, MUTATOR_ARGV_0_float) \
+/** allows overriding of team counts */
+#define EV_TeamBalance_GetTeamCount(i, o) \
+    /** team index to count             */ i(float, MUTATOR_ARGV_0_float) \
     /** player to ignore                */ i(entity, MUTATOR_ARGV_1_entity) \
-    /** number of players in a team     */ i(float, MUTATOR_ARGV_2_float) \
-    /**/                                   o(float, MUTATOR_ARGV_2_float) \
-    /** number of bots in a team        */ i(float, MUTATOR_ARGV_3_float) \
-    /**/                                   o(float, MUTATOR_ARGV_3_float) \
-    /** lowest scoring human in a team  */ i(entity, MUTATOR_ARGV_4_entity) \
-    /**/                                   o(entity, MUTATOR_ARGV_4_entity) \
-    /** lowest scoring bot in a team    */ i(entity, MUTATOR_ARGV_5_entity) \
-    /**/                                   o(entity, MUTATOR_ARGV_5_entity) \
-    /**/
-MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
-
-/** allows overriding best teams */
-#define EV_FindBestTeams(i, o) \
+    /** number of players in a team     */ o(float, MUTATOR_ARGV_2_float) \
+    /** number of bots in a team        */ o(float, MUTATOR_ARGV_3_float) \
+    /**/
+MUTATOR_HOOKABLE(TeamBalance_GetTeamCount, EV_TeamBalance_GetTeamCount);
+
+/** allows overriding the teams that will make the game most balanced if the
+ *  player joins any of them.
+ */
+#define EV_TeamBalance_FindBestTeams(i, o) \
     /** player checked   */ i(entity, MUTATOR_ARGV_0_entity) \
     /** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \
     /**/
-MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams);
+MUTATOR_HOOKABLE(TeamBalance_FindBestTeams, EV_TeamBalance_FindBestTeams);
+
+/** Called during autobalance. Return true to override the player that will be
+switched. */
+#define EV_TeamBalance_GetPlayerForTeamSwitch(i, o) \
+    /** source team index      */ i(int, MUTATOR_ARGV_0_int) \
+    /** destination team index */ i(int, MUTATOR_ARGV_1_int) \
+    /** is looking for bot     */ i(bool, MUTATOR_ARGV_2_bool) \
+    /** player to switch       */ o(entity, MUTATOR_ARGV_3_entity) \
+    /**/
+MUTATOR_HOOKABLE(TeamBalance_GetPlayerForTeamSwitch,
+       EV_TeamBalance_GetPlayerForTeamSwitch);
 
 /** copies variables for spectating "spectatee" to "this" */
 #define EV_SpectateCopy(i, o) \
@@ -1041,9 +1052,9 @@ MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel);
  * Called before player changes their team. Return true to block team change.
  */
 #define EV_Player_ChangeTeam(i, o) \
-    /** player */         i(entity, MUTATOR_ARGV_0_entity) \
-       /** current team */   i(float, MUTATOR_ARGV_1_float) \
-       /** new team */       i(float, MUTATOR_ARGV_2_float) \
+    /** player */             i(entity, MUTATOR_ARGV_0_entity) \
+    /** current team index */ i(float, MUTATOR_ARGV_1_float) \
+    /** new team index */     i(float, MUTATOR_ARGV_2_float) \
     /**/
 MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
 
@@ -1051,9 +1062,9 @@ MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
  * Called after player has changed their team.
  */
 #define EV_Player_ChangedTeam(i, o) \
-    /** player */         i(entity, MUTATOR_ARGV_0_entity) \
-       /** old team */       i(float, MUTATOR_ARGV_1_float) \
-       /** current team */   i(float, MUTATOR_ARGV_2_float) \
+    /** player */             i(entity, MUTATOR_ARGV_0_entity) \
+    /** old team index */     i(float, MUTATOR_ARGV_1_float) \
+    /** current team index */ i(float, MUTATOR_ARGV_2_float) \
     /**/
 MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam);
 
@@ -1192,3 +1203,12 @@ MUTATOR_HOOKABLE(Freeze, EV_Freeze);
     /** targ */             i(entity, MUTATOR_ARGV_0_entity) \
     /**/
 MUTATOR_HOOKABLE(Unfreeze, EV_Unfreeze);
+
+/**
+ * Called when a player is trying to join, argument is the number of players allowed to join the match
+ */
+#define EV_GetPlayerLimit(i, o) \
+    /** g_maxplayers */             i(int, MUTATOR_ARGV_0_int) \
+    /**/                            o(int, MUTATOR_ARGV_0_int) \
+    /**/
+MUTATOR_HOOKABLE(GetPlayerLimit, EV_GetPlayerLimit);
index 5bd4b59894e964fcd6b0850a3f67232c8f38a79c..3805df3f26126375a64a3fc52ce3748c0d362de7 100644 (file)
@@ -3,6 +3,7 @@
 #include <common/effects/all.qh>
 #include "bot/api.qh"
 #include "cheats.qh"
+#include "clientkill.qh"
 #include "g_damage.qh"
 #include "handicap.qh"
 #include "miscfunctions.qh"
@@ -22,6 +23,8 @@
 
 #include "../common/minigames/sv_minigames.qh"
 
+#include <common/gamemodes/_mod.qh>
+
 #include "../common/physics/player.qh"
 #include "../common/effects/qc/_mod.qh"
 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
@@ -465,7 +468,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                        if (this != attacker) {
                                float realdmg = damage - excess;
-                               if (IS_PLAYER(attacker)) {
+                               if (IS_PLAYER(attacker) && DIFF_TEAM(attacker, this)) {
                                        GameRules_scoring_add(attacker, DMG, realdmg);
                                }
                                if (IS_PLAYER(this)) {
@@ -550,8 +553,8 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                if(this.classname != "body")
                        Obituary (attacker, inflictor, this, deathtype, weaponentity);
 
-        // increment frag counter for used weapon type
-        Weapon w = DEATH_WEAPONOF(deathtype);
+               // increment frag counter for used weapon type
+               Weapon w = DEATH_WEAPONOF(deathtype);
                if(w != WEP_Null && accuracy_isgooddamage(attacker, this))
                        CS(attacker).accuracy.(accuracy_frags[w.m_id-1]) += 1;
 
@@ -589,8 +592,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                // when we get here, player actually dies
 
-               Unfreeze(this); // remove any icy remains
-               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // Unfreeze resets health, so we need to set it back
+               Unfreeze(this, false); // remove any icy remains
 
                // clear waypoints
                WaypointSprite_PlayerDead(this);
@@ -674,318 +676,3 @@ bool PlayerHeal(entity targ, entity inflictor, float amount, float limit)
        GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, limit);
        return true;
 }
-
-bool MoveToTeam(entity client, int team_colour, int type)
-{
-       int lockteams_backup = lockteams;  // backup any team lock
-       lockteams = 0;  // disable locked teams
-       TeamchangeFrags(client);  // move the players frags
-       if (!SetPlayerTeamSimple(client, team_colour))
-       {
-               return false;
-       }
-       Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, DMG_NOWEP, client.origin, '0 0 0');  // kill the player
-       lockteams = lockteams_backup;  // restore the team lock
-       LogTeamchange(client.playerid, client.team, type);
-       return true;
-}
-
-/**
- * message "": do not say, just test flood control
- * return value:
- *   1 = accept
- *   0 = reject
- *  -1 = fake accept
- */
-int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
-{
-       if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
-        msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
-
-    if(source)
-               msgin = formatmessage(source, msgin);
-
-    string colorstr;
-       if (!IS_PLAYER(source))
-               colorstr = "^0"; // black for spectators
-       else if(teamplay)
-               colorstr = Team_ColorCode(source.team);
-       else
-       {
-               colorstr = "";
-               teamsay = false;
-       }
-
-       if(game_stopped)
-               teamsay = false;
-
-    if (!source) {
-               colorstr = "";
-               teamsay = false;
-    }
-
-       if(msgin != "")
-               msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
-
-       /*
-        * using bprint solves this... me stupid
-       // how can we prevent the message from appearing in a listen server?
-       // for now, just give "say" back and only handle say_team
-       if(!teamsay)
-       {
-               clientcommand(source, strcat("say ", msgin));
-               return;
-       }
-       */
-
-    string namestr = "";
-    if (source)
-        namestr = playername(source, autocvar_g_chat_teamcolors);
-
-    string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
-
-    string msgstr, cmsgstr;
-    string privatemsgprefix = string_null;
-    int privatemsgprefixlen = 0;
-       if (msgin == "") {
-        msgstr = cmsgstr = "";
-       } else {
-               if(privatesay)
-               {
-                       msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
-                       privatemsgprefixlen = strlen(msgstr);
-                       msgstr = strcat(msgstr, msgin);
-                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
-                       privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
-               }
-               else if(teamsay)
-               {
-                       if(strstrofs(msgin, "/me", 0) >= 0)
-                       {
-                               //msgin = strreplace("/me", "", msgin);
-                               //msgin = substring(msgin, 3, strlen(msgin));
-                               msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
-                               msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
-                       }
-                       else
-                               msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
-                       cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
-               }
-               else
-               {
-                       if(strstrofs(msgin, "/me", 0) >= 0)
-                       {
-                               //msgin = strreplace("/me", "", msgin);
-                               //msgin = substring(msgin, 3, strlen(msgin));
-                               msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
-                               msgstr = strcat("\{1}^4* ", "^7", msgin);
-                       }
-                       else {
-                msgstr = "\{1}";
-                msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
-                msgstr = strcat(msgstr, msgin);
-            }
-                       cmsgstr = "";
-               }
-               msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
-       }
-
-       string fullmsgstr = msgstr;
-       string fullcmsgstr = cmsgstr;
-
-       // FLOOD CONTROL
-       int flood = 0;
-       var .float flood_field = floodcontrol_chat;
-       if(floodcontrol && source)
-       {
-               float flood_spl;
-               float flood_burst;
-               float flood_lmax;
-               float lines;
-               if(privatesay)
-               {
-                       flood_spl = autocvar_g_chat_flood_spl_tell;
-                       flood_burst = autocvar_g_chat_flood_burst_tell;
-                       flood_lmax = autocvar_g_chat_flood_lmax_tell;
-                       flood_field = floodcontrol_chattell;
-               }
-               else if(teamsay)
-               {
-                       flood_spl = autocvar_g_chat_flood_spl_team;
-                       flood_burst = autocvar_g_chat_flood_burst_team;
-                       flood_lmax = autocvar_g_chat_flood_lmax_team;
-                       flood_field = floodcontrol_chatteam;
-               }
-               else
-               {
-                       flood_spl = autocvar_g_chat_flood_spl;
-                       flood_burst = autocvar_g_chat_flood_burst;
-                       flood_lmax = autocvar_g_chat_flood_lmax;
-                       flood_field = floodcontrol_chat;
-               }
-               flood_burst = max(0, flood_burst - 1);
-               // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
-
-               // do flood control for the default line size
-               if(msgstr != "")
-               {
-                       getWrappedLine_remaining = msgstr;
-                       msgstr = "";
-                       lines = 0;
-                       while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
-                       {
-                               msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
-                               ++lines;
-                       }
-                       msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
-
-                       if(getWrappedLine_remaining != "")
-                       {
-                               msgstr = strcat(msgstr, "\n");
-                               flood = 2;
-                       }
-
-                       if (time >= source.(flood_field))
-                       {
-                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
-                       }
-                       else
-                       {
-                               flood = 1;
-                               msgstr = fullmsgstr;
-                       }
-               }
-               else
-               {
-                       if (time >= source.(flood_field))
-                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
-                       else
-                               flood = 1;
-               }
-
-               if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
-                       source.(flood_field) = flood = 0;
-       }
-
-    string sourcemsgstr, sourcecmsgstr;
-       if(flood == 2) // cannot happen for empty msgstr
-       {
-               if(autocvar_g_chat_flood_notify_flooder)
-               {
-                       sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
-                       sourcecmsgstr = "";
-               }
-               else
-               {
-                       sourcemsgstr = fullmsgstr;
-                       sourcecmsgstr = fullcmsgstr;
-               }
-               cmsgstr = "";
-       }
-       else
-       {
-               sourcemsgstr = msgstr;
-               sourcecmsgstr = cmsgstr;
-       }
-
-       if (!privatesay && source && !IS_PLAYER(source))
-       {
-               if (!game_stopped)
-               if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
-                       teamsay = -1; // spectators
-       }
-
-       if(flood)
-               LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.");
-
-       // build sourcemsgstr by cutting off a prefix and replacing it by the other one
-       if(privatesay)
-               sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
-
-    int ret;
-       if(source && CS(source).muted)
-       {
-               // always fake the message
-               ret = -1;
-       }
-       else if(flood == 1)
-       {
-               if (autocvar_g_chat_flood_notify_flooder)
-               {
-                       sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
-                       ret = 0;
-               }
-               else
-                       ret = -1;
-       }
-       else
-       {
-               ret = 1;
-       }
-
-       if (privatesay && source && !IS_PLAYER(source))
-       {
-               if (!game_stopped)
-               if ((privatesay && IS_PLAYER(privatesay)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
-                       ret = -1; // just hide the message completely
-       }
-
-       MUTATOR_CALLHOOK(ChatMessage, source, ret);
-       ret = M_ARGV(1, int);
-
-       if(sourcemsgstr != "" && ret != 0)
-       {
-               if(ret < 0) // faked message, because the player is muted
-               {
-                       sprint(source, sourcemsgstr);
-                       if(sourcecmsgstr != "" && !privatesay)
-                               centerprint(source, sourcecmsgstr);
-               }
-               else if(privatesay) // private message, between 2 people only
-               {
-                       sprint(source, sourcemsgstr);
-                       if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
-                       if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
-                       {
-                               sprint(privatesay, msgstr);
-                               if(cmsgstr != "")
-                                       centerprint(privatesay, cmsgstr);
-                       }
-               }
-               else if ( teamsay && CS(source).active_minigame )
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
-               }
-               else if(teamsay > 0) // team message, only sent to team mates
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       if(sourcecmsgstr != "")
-                               centerprint(source, sourcecmsgstr);
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
-                               sprint(it, msgstr);
-                               if(cmsgstr != "")
-                                       centerprint(it, cmsgstr);
-                       });
-               }
-               else if(teamsay < 0) // spectator message, only sent to spectators
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
-               }
-               else
-               {
-            if (source) {
-                sprint(source, sourcemsgstr);
-                dedicated_print(msgstr); // send to server console too
-                MX_Say(strcat(playername(source, true), "^7: ", msgin));
-            }
-            FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
-        }
-       }
-
-       return ret;
-}
index 8c9bac9b62aaa8e94ea4a594e33f6b680f67d74c..514b34726c2edf295dc9c79e00bfca8f6dbc5f00 100644 (file)
@@ -2,7 +2,7 @@
 
 .entity pusher;
 .float pushltime;
-.float istypefrag;
+.bool istypefrag;
 
 .float CopyBody_nextthink;
 .void(entity this) CopyBody_think;
@@ -28,17 +28,6 @@ void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float da
 
 void calculate_player_respawn_time(entity this);
 
-void ClientKill_Now_TeamChange(entity this);
-
-/// \brief Moves player to the specified team.
-/// \param[in,out] client Client to move.
-/// \param[in] team_colour Color of the team.
-/// \param[in] type ???
-/// \return True on success, false otherwise.
-bool MoveToTeam(entity client, float team_colour, float type);
-
 void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
 
 bool PlayerHeal(entity targ, entity inflictor, float amount, float limit);
-
-int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
index 162026a169e98843d259e2717228984fb06905b8..99f0c0d82e67ae65a15fe1b229efb3885dbe073b 100644 (file)
@@ -198,7 +198,7 @@ float Portal_TeleportPlayer(entity teleporter, entity player)
 
        // reset fade counter
        teleporter.portal_wants_to_vanish = 0;
-       teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
+       teleporter.fade_time = ((autocvar_g_balance_portal_lifetime >= 0) ? time + autocvar_g_balance_portal_lifetime : 0);
        SetResourceAmountExplicit(teleporter, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
        SetResourceAmountExplicit(teleporter.enemy, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
 
@@ -383,7 +383,7 @@ void Portal_Connect(entity teleporter, entity destination)
        destination.enemy = teleporter;
        Portal_MakeInPortal(teleporter);
        Portal_MakeOutPortal(destination);
-       teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
+       teleporter.fade_time = ((autocvar_g_balance_portal_lifetime >= 0) ? time + autocvar_g_balance_portal_lifetime : 0);
        destination.fade_time = teleporter.fade_time;
        teleporter.portal_wants_to_vanish = 0;
        destination.portal_wants_to_vanish = 0;
@@ -494,7 +494,7 @@ void Portal_Think(entity this)
 
        this.nextthink = time;
 
-       if(time > this.fade_time)
+       if(this.fade_time && time > this.fade_time)
                Portal_Remove(this, 0);
 }
 
@@ -638,7 +638,7 @@ entity Portal_Spawn(entity own, vector org, vector ang)
        portal.portal_activatetime = time + 0.1;
        portal.takedamage = DAMAGE_AIM;
        portal.event_damage = Portal_Damage;
-       portal.fade_time = time + autocvar_g_balance_portal_lifetime;
+       portal.fade_time = ((autocvar_g_balance_portal_lifetime >= 0) ? time + autocvar_g_balance_portal_lifetime : 0);
        SetResourceAmountExplicit(portal, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
        setmodel(portal, MDL_PORTAL);
        portal.savemodelindex = portal.modelindex;
index ae64e74e4909d66882ea09e261926086977b4da0..d347fcbaf9be95d010a8af0378795ffe3c9fe315 100644 (file)
@@ -86,7 +86,7 @@ void round_handler_FirstThink(entity this)
        this.nextthink = max(time, game_starttime);
 }
 
-void round_handler_Spawn(float() canRoundStart_func, float() canRoundEnd_func, void() roundStart_func)
+void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func)
 {
        if (round_handler)
        {
index e6b17b0e775d76f1868c05d592c633844b1b8b8b..5979eb5c33f810faeb55f2add99f7574533fc121 100644 (file)
@@ -3,17 +3,17 @@
 entity round_handler;
 .float delay; // stores delay from round end to countdown start
 .float count; // stores initial number of the countdown
-.float wait; // it's set to true when round ends, to false when countdown starts
+.bool wait; // it's set to true when round ends, to false when countdown starts
 .float cnt;    // its initial value is .count + 1, then decreased while counting down
                        // reaches 0 when the round starts
 .float round_timelimit;
 .float round_endtime;
-.float() canRoundStart;
-.float() canRoundEnd;
+.bool() canRoundStart;
+.bool() canRoundEnd;
 .void() roundStart;
 
 void round_handler_Init(float the_delay, float the_count, float the_round_timelimit);
-void round_handler_Spawn(float() canRoundStart_func, float() canRoundEnd_func, void() roundStart_func);
+void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func);
 void round_handler_Reset(float next_think);
 void round_handler_Remove();
 
index 160b5df5e4cfb21c279b96c39d28dbe6965b27d4..39dbd49a35ddc630c85c849624330e1785d30d01 100644 (file)
@@ -17,8 +17,6 @@ int NumTeams(int teams)
 int AvailableTeams()
 {
        return NumTeams(ScoreRules_teams);
-       // NOTE: this method is unreliable, as forced teams set the c* globals to weird values
-       //return boolean(c1 >= 0) + boolean(c2 >= 0) + boolean(c3 >= 0) + boolean(c4 >= 0);
 }
 
 // NOTE: ST_constants may not be >= MAX_TEAMSCORE
@@ -64,12 +62,11 @@ void ScoreRules_basics_end()
 void ScoreRules_generic()
 {
     int teams = 0;
-       if (teamplay) {
-               CheckAllowedTeams(NULL);
-               if (c1 >= 0) teams |= BIT(0);
-               if (c2 >= 0) teams |= BIT(1);
-               if (c3 >= 0) teams |= BIT(2);
-               if (c4 >= 0) teams |= BIT(3);
+       if (teamplay)
+       {
+               entity balance = TeamBalance_CheckAllowedTeams(NULL);
+               teams = TeamBalance_GetAllowedTeams(balance);
+               TeamBalance_Destroy(balance);
        }
        GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {});
 }
index e0e33e4803c87445641359a8c76824cc821a19a8..03b40c5e12864aa7570c19ff0169c0ec107162b2 100644 (file)
@@ -248,7 +248,7 @@ vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck)
        vector spawn_score = prio * '1 0 0' + shortest * '0 1 0';
 
        // filter out spots for assault
-       if(spot.target != "")
+       if(spot.target && spot.target != "")
        {
                int found = 0;
                for(entity targ = findchain(targetname, spot.target); targ; targ = targ.chain)
index b0d9e0992eb921c204451f24b48877010c082369..239fb69f0ca8e0e4b20b1da78d8e44897b4e6e5b 100644 (file)
 #include <common/gamemodes/_mod.qh>
 #include "../common/teams.qh"
 
-void TeamchangeFrags(entity e)
+/// \brief Describes a state of team balance entity.
+enum
 {
-       PlayerScore_Clear(e);
-}
+       TEAM_BALANCE_UNINITIALIZED, ///< The team balance has not been initialized.
+       /// \brief TeamBalance_CheckAllowedTeams has been called.
+       TEAM_BALANCE_TEAMS_CHECKED,
+       /// \brief TeamBalance_GetTeamCounts has been called.
+       TEAM_BALANCE_TEAM_COUNTS_FILLED
+};
 
-void LogTeamchange(float player_id, float team_number, float type)
-{
-       if(!autocvar_sv_eventlog)
-               return;
+/// \brief Indicates that the player is not allowed to join a team.
+const int TEAM_NOT_ALLOWED = -1;
 
-       if(player_id < 1)
-               return;
+.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
 
-       GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
-}
+.int m_team_balance_state; ///< Holds the state of the team balance entity.
+.entity m_team_balance_team[NUM_TEAMS]; ///< ???
 
-void default_delayedinit(entity this)
-{
-       if(!scores_initialized)
-               ScoreRules_generic();
-}
+.float m_team_score; ///< The score of the team.
+.int m_num_players; ///< Number of players (both humans and bots) in a team.
+.int m_num_bots; ///< Number of bots in a team.
+.int m_num_players_alive; ///< Number of alive players in a team.
+.int m_num_control_points; ///< Number of control points owned by a team.
 
-void InitGameplayMode()
-{
-       VoteReset();
-
-       // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
-       get_mi_min_max(1);
-       // assign reflectively to avoid "assignment to world" warning
-       int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
-           string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
-           if (v) {
-            putentityfieldstring(i, world, sprintf("%v", v));
-            if (++done == 2) break;
-        }
-       }
-       // currently, NetRadiant's limit is 131072 qu for each side
-       // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
-       // set the distance according to map size but don't go over the limit to avoid issues with float precision
-       // in case somebody makes extremely large maps
-       max_shot_distance = min(230000, vlen(world.maxs - world.mins));
-
-       MapInfo_LoadMapSettings(mapname);
-       GameRules_teams(false);
-
-       if (!cvar_value_issafe(world.fog))
-       {
-               LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
-               world.fog = string_null;
-       }
-       if(MapInfo_Map_fog != "")
-               if(MapInfo_Map_fog == "none")
-                       world.fog = string_null;
-               else
-                       world.fog = strzone(MapInfo_Map_fog);
-       clientstuff = strzone(MapInfo_Map_clientstuff);
+string autocvar_g_forced_team_red;
+string autocvar_g_forced_team_blue;
+string autocvar_g_forced_team_yellow;
+string autocvar_g_forced_team_pink;
 
-       MapInfo_ClearTemps();
+entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities.
 
-       gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
-
-       cache_mutatormsg = strzone("");
-       cache_lastmutatormsg = strzone("");
-
-       InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
+STATIC_INIT(g_team_entities)
+{
+       for (int i = 0; i < NUM_TEAMS; ++i)
+       {
+               g_team_entities[i] = spawn();
+       }
 }
 
-string GetClientVersionMessage(entity this)
+entity Team_GetTeamFromIndex(int index)
 {
-       if (CS(this).version_mismatch) {
-               if(CS(this).version < autocvar_gameversion) {
-                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
-                               "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
-               } else {
-                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
-                               "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
-               }
-       } else {
-               return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
+       if (!Team_IsValidIndex(index))
+       {
+               LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index);
        }
+       return g_team_entities[index - 1];
 }
 
-string getwelcomemessage(entity this)
+entity Team_GetTeam(int team_num)
 {
-       MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
-       string modifications = M_ARGV(0, string);
-
-       if(g_weaponarena)
+       if (!Team_IsValidTeam(team_num))
        {
-               if(g_weaponarena_random)
-                       modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena"); // TODO: somehow get this into the mutator
-               else
-                       modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
+               LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num);
        }
-       else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
-               modifications = strcat(modifications, ", No start weapons");
-       if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
-               modifications = strcat(modifications, ", Low gravity");
-       if(g_weapon_stay && !g_cts)
-               modifications = strcat(modifications, ", Weapons stay");
-       if(g_jetpack)
-               modifications = strcat(modifications, ", Jetpack");
-       if(autocvar_g_powerups == 0)
-               modifications = strcat(modifications, ", No powerups");
-       if(autocvar_g_powerups > 0)
-               modifications = strcat(modifications, ", Powerups");
-       modifications = substring(modifications, 2, strlen(modifications) - 2);
+       return g_team_entities[Team_TeamToIndex(team_num) - 1];
+}
 
-       string versionmessage = GetClientVersionMessage(this);
-       string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
+float Team_GetTeamScore(entity team_ent)
+{
+       return team_ent.m_team_score;
+}
 
-       if(modifications != "")
-               s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+void Team_SetTeamScore(entity team_ent, float score)
+{
+       team_ent.m_team_score = score;
+}
 
-       if(cache_lastmutatormsg != autocvar_g_mutatormsg)
-       {
-               strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
-               strcpy(cache_mutatormsg, cache_lastmutatormsg);
-       }
+int Team_GetNumberOfAlivePlayers(entity team_ent)
+{
+       return team_ent.m_num_players_alive;
+}
+
+void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
+{
+       team_ent.m_num_players_alive = number;
+}
 
-       if (cache_mutatormsg != "") {
-               s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
+int Team_GetNumberOfAliveTeams()
+{
+       int result = 0;
+       for (int i = 0; i < NUM_TEAMS; ++i)
+       {
+               if (g_team_entities[i].m_num_players_alive > 0)
+               {
+                       ++result;
+               }
        }
+       return result;
+}
 
-       string mutator_msg = "";
-       MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
-       mutator_msg = M_ARGV(0, string);
+int Team_GetNumberOfControlPoints(entity team_ent)
+{
+       return team_ent.m_num_control_points;
+}
 
-       s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
+void Team_SetNumberOfControlPoints(entity team_ent, int number)
+{
+       team_ent.m_num_control_points = number;
+}
 
-       string motd = autocvar_sv_motd;
-       if (motd != "") {
-               s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+int Team_GetNumberOfTeamsWithControlPoints()
+{
+       int result = 0;
+       for (int i = 0; i < NUM_TEAMS; ++i)
+       {
+               if (g_team_entities[i].m_num_control_points > 0)
+               {
+                       ++result;
+               }
        }
-       return s;
+       return result;
 }
 
 void setcolor(entity this, int clr)
@@ -162,6 +138,26 @@ void setcolor(entity this, int clr)
 #endif
 }
 
+bool Entity_HasValidTeam(entity this)
+{
+       return Team_IsValidTeam(this.team);
+}
+
+int Entity_GetTeamIndex(entity this)
+{
+       return Team_TeamToIndex(this.team);
+}
+
+entity Entity_GetTeam(entity this)
+{
+       int index = Entity_GetTeamIndex(this);
+       if (!Team_IsValidIndex(index))
+       {
+               return NULL;
+       }
+       return Team_GetTeamFromIndex(index);
+}
+
 void SetPlayerColors(entity player, float _color)
 {
        float pants = _color & 0x0F;
@@ -176,901 +172,970 @@ void SetPlayerColors(entity player, float _color)
        }
 }
 
-void KillPlayerForTeamChange(entity player)
+bool Player_SetTeamIndex(entity player, int index)
 {
-       if (IS_DEAD(player))
+       int new_team = Team_IndexToTeam(index);
+       if (player.team == new_team)
        {
-               return;
+               if (new_team != -1)
+               {
+                       // This is important when players join the game and one of their
+                       // color matches the team color while other doesn't. For example
+                       // [BOT]Lion.
+                       SetPlayerColors(player, new_team - 1);
+               }
+               return true;
        }
-       if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
+       int old_index = Team_TeamToIndex(player.team);
+       if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, old_index, index) == true)
        {
-               return;
+               // Mutator has blocked team change.
+               return false;
+       }
+       if (new_team == -1)
+       {
+               player.team = -1;
+       }
+       else
+       {
+               SetPlayerColors(player, new_team - 1);
        }
-       Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP, player.origin,
-               '0 0 0');
+       MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_index, index);
+       return true;
 }
 
-bool SetPlayerTeamSimple(entity player, int team_num)
+bool SetPlayerTeam(entity player, int team_index, int type)
 {
-       if (player.team == team_num)
+       int old_team_index = Entity_GetTeamIndex(player);
+       if (!Player_SetTeamIndex(player, team_index))
        {
-               // This is important when players join the game and one of their color
-               // matches the team color while other doesn't. For example [BOT]Lion.
-               SetPlayerColors(player, team_num - 1);
-               return true;
+               return false;
        }
-       if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
-               player.team), Team_TeamToNumber(team_num)) == true)
+       LogTeamChange(player.playerid, player.team, type);
+       if (team_index != old_team_index)
        {
-               // Mutator has blocked team change.
-               return false;
+               PlayerScore_Clear(player);
+               if (team_index != -1)
+               {
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(
+                               player.team, INFO_JOIN_PLAY_TEAM), player.netname);
+               }
+               else
+               {
+                       if (!CS(player).just_joined)
+                       {
+                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE,
+                                       player.netname);
+                       }
+                       else
+                       {
+                               CS(player).just_joined = false;
+                       }
+               }
+               KillPlayerForTeamChange(player);
+               if (!IS_BOT_CLIENT(player))
+               {
+                       TeamBalance_AutoBalanceBots();
+               }
        }
-       int old_team = player.team;
-       SetPlayerColors(player, team_num - 1);
-       MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team, player.team);
        return true;
 }
 
-bool SetPlayerTeam(entity player, int destination_team, int source_team,
-       bool no_print)
+void Player_SetTeamIndexChecked(entity player, int team_index)
 {
-       int team_num = Team_NumberToTeam(destination_team);
-       if (!SetPlayerTeamSimple(player, team_num))
+       if (!teamplay)
        {
-               return false;
+               return;
        }
-       LogTeamchange(player.playerid, player.team, 3);  // log manual team join
-       if (no_print)
+       if (!Team_IsValidIndex(team_index))
        {
-               return true;
+               return;
        }
-       bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(source_team), "^7 to ", Team_NumberToColoredFullName(destination_team), "\n");
+       if ((autocvar_g_campaign) || (autocvar_g_changeteam_banned &&
+               CS(player).wasplayer))
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_INFO,
+                       INFO_TEAMCHANGE_NOTALLOWED);
+               return;
+       }
+       entity balance = TeamBalance_CheckAllowedTeams(player);
+       if (team_index == 1 && !TeamBalance_IsTeamAllowedInternal(balance, 1))
+       {
+               team_index = 4;
+       }
+       if (team_index == 4 && !TeamBalance_IsTeamAllowedInternal(balance, 4))
+       {
+               team_index = 3;
+       }
+       if (team_index == 3 && !TeamBalance_IsTeamAllowedInternal(balance, 3))
+       {
+               team_index = 2;
+       }
+       if (team_index == 2 && !TeamBalance_IsTeamAllowedInternal(balance, 2))
+       {
+               team_index = 1;
+       }
+       // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
+       if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
+       {
+               TeamBalance_GetTeamCounts(balance, player);
+               if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance,
+                       player, false)) == 0)
+               {
+                       Send_Notification(NOTIF_ONE, player, MSG_INFO,
+                               INFO_TEAMCHANGE_LARGERTEAM);
+                       TeamBalance_Destroy(balance);
+                       return;
+               }
+       }
+       TeamBalance_Destroy(balance);
+       SetPlayerTeam(player, team_index, TEAM_CHANGE_MANUAL);
+}
+
+bool MoveToTeam(entity client, int team_index, int type)
+{
+       //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
+       int lockteams_backup = lockteams;  // backup any team lock
+       lockteams = 0;  // disable locked teams
+       if (!SetPlayerTeam(client, team_index, type))
+       {
+               lockteams = lockteams_backup;  // restore the team lock
+               return false;
+       }
+       lockteams = lockteams_backup;  // restore the team lock
        return true;
 }
 
-// set c1...c4 to show what teams are allowed
-void CheckAllowedTeams(entity for_whom)
+bool Player_HasRealForcedTeam(entity player)
 {
-       int teams_mask = 0;
+       return player.team_forced > TEAM_FORCE_DEFAULT;
+}
 
-       c1 = c2 = c3 = c4 = -1;
-       num_bots_team1 = num_bots_team2 = num_bots_team3 = num_bots_team4 = 0;
+int Player_GetForcedTeamIndex(entity player)
+{
+       return player.team_forced;
+}
 
-       string teament_name = string_null;
+void Player_SetForcedTeamIndex(entity player, int team_index)
+{
+       switch (team_index)
+       {
+               case TEAM_FORCE_SPECTATOR:
+               case TEAM_FORCE_DEFAULT:
+               {
+                       player.team_forced = team_index;
+                       break;
+               }
+               default:
+               {
+                       if (!Team_IsValidIndex(team_index))
+                       {
+                               LOG_FATAL("Player_SetForcedTeamIndex: Invalid team index.");
+                       }
+                       else
+                       {
+                               player.team_forced = team_index;
+                               break;
+                       }
+               }
+       }
+}
 
-       bool mutator_returnvalue = MUTATOR_CALLHOOK(CheckAllowedTeams, teams_mask, teament_name, for_whom);
+void Player_DetermineForcedTeam(entity player)
+{
+       if (autocvar_g_campaign)
+       {
+               if (IS_REAL_CLIENT(player)) // only players, not bots
+               {
+                       if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
+                       {
+                               player.team_forced = autocvar_g_campaign_forceteam;
+                       }
+                       else
+                       {
+                               player.team_forced = TEAM_FORCE_DEFAULT;
+                       }
+               }
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_red))
+       {
+               player.team_forced = 1;
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_blue))
+       {
+               player.team_forced = 2;
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_yellow))
+       {
+               player.team_forced = 3;
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_pink))
+       {
+               player.team_forced = 4;
+       }
+       else
+       {
+               switch (autocvar_g_forced_team_otherwise)
+               {
+                       case "red":
+                       {
+                               player.team_forced = 1;
+                               break;
+                       }
+                       case "blue":
+                       {
+                               player.team_forced = 2;
+                               break;
+                       }
+                       case "yellow":
+                       {
+                               player.team_forced = 3;
+                               break;
+                       }
+                       case "pink":
+                       {
+                               player.team_forced = 4;
+                               break;
+                       }
+                       case "spectate":
+                       case "spectator":
+                       {
+                               player.team_forced = TEAM_FORCE_SPECTATOR;
+                               break;
+                       }
+                       default:
+                       {
+                               player.team_forced = TEAM_FORCE_DEFAULT;
+                               break;
+                       }
+               }
+       }
+       if (!teamplay && Player_HasRealForcedTeam(player))
+       {
+               player.team_forced = TEAM_FORCE_DEFAULT;
+       }
+}
+
+void TeamBalance_JoinBestTeam(entity player)
+{
+       //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
+       if (!teamplay)
+       {
+               return;
+       }
+       if (player.bot_forced_team)
+       {
+               return;
+       }
+       entity balance = TeamBalance_CheckAllowedTeams(player);
+       if (Player_HasRealForcedTeam(player))
+       {
+               int forced_team_index = player.team_forced;
+               bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
+                       forced_team_index);
+               TeamBalance_Destroy(balance);
+               if (!is_team_allowed)
+               {
+                       return;
+               }
+               if (!SetPlayerTeam(player, forced_team_index, TEAM_CHANGE_AUTO))
+               {
+                       return;
+               }
+               return;
+       }
+       int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
+       TeamBalance_Destroy(balance);
+       if (!SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO))
+       {
+               return;
+       }
+}
+
+entity TeamBalance_CheckAllowedTeams(entity for_whom)
+{
+       entity balance = spawn();
+       for (int i = 0; i < NUM_TEAMS; ++i)
+       {
+               entity team_ent = balance.m_team_balance_team[i] = spawn();
+               team_ent.m_team_score = g_team_entities[i].m_team_score;
+               team_ent.m_num_players = TEAM_NOT_ALLOWED;
+               team_ent.m_num_bots = 0;
+       }
+       setthink(balance, TeamBalance_Destroy);
+       
+       int teams_mask = 0;     
+       string teament_name = string_null;
+       bool mutator_returnvalue = MUTATOR_CALLHOOK(TeamBalance_CheckAllowedTeams,
+               teams_mask, teament_name, for_whom);
        teams_mask = M_ARGV(0, float);
        teament_name = M_ARGV(1, string);
-
-       if(!mutator_returnvalue)
+       if (mutator_returnvalue)
        {
-               if(teams_mask & BIT(0)) c1 = 0;
-               if(teams_mask & BIT(1)) c2 = 0;
-               if(teams_mask & BIT(2)) c3 = 0;
-               if(teams_mask & BIT(3)) c4 = 0;
+               for (int i = 0; i < NUM_TEAMS; ++i)
+               {
+                       if (teams_mask & BIT(i))
+                       {
+                               balance.m_team_balance_team[i].m_num_players = 0;
+                       }
+               }
        }
 
-       // find out what teams are allowed if necessary
-       if(teament_name)
+       if (teament_name)
        {
                entity head = find(NULL, classname, teament_name);
-               while(head)
+               while (head)
                {
-                       switch(head.team)
+                       if (Team_IsValidTeam(head.team))
                        {
-                               case NUM_TEAM_1: c1 = 0; break;
-                               case NUM_TEAM_2: c2 = 0; break;
-                               case NUM_TEAM_3: c3 = 0; break;
-                               case NUM_TEAM_4: c4 = 0; break;
+                               TeamBalance_GetTeam(balance, head.team).m_num_players = 0;
                        }
-
                        head = find(head, classname, teament_name);
                }
        }
 
        // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
-       if(AvailableTeams() == 2)
-       if(autocvar_bot_vs_human && for_whom)
+       if (AvailableTeams() == 2)
+       if (autocvar_bot_vs_human && for_whom)
        {
-               if(autocvar_bot_vs_human > 0)
+               if (autocvar_bot_vs_human > 0)
                {
                        // find last team available
-
-                       if(IS_BOT_CLIENT(for_whom))
+                       if (IS_BOT_CLIENT(for_whom))
                        {
-                               if(c4 >= 0) { c3 = c2 = c1 = -1; }
-                               else if(c3 >= 0) { c4 = c2 = c1 = -1; }
-                               else { c4 = c3 = c1 = -1; }
+                               if (TeamBalance_IsTeamAllowedInternal(balance, 4))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 4);
+                               }
+                               else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 3);
+                               }
+                               else
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 2);
+                               }
                                // no further cases, we know at least 2 teams exist
                        }
                        else
                        {
-                               if(c1 >= 0) { c2 = c3 = c4 = -1; }
-                               else if(c2 >= 0) { c1 = c3 = c4 = -1; }
-                               else { c1 = c2 = c4 = -1; }
+                               if (TeamBalance_IsTeamAllowedInternal(balance, 1))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 1);
+                               }
+                               else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 2);
+                               }
+                               else
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 3);
+                               }
                                // no further cases, bots have one of the teams
                        }
                }
                else
                {
                        // find first team available
-
-                       if(IS_BOT_CLIENT(for_whom))
+                       if (IS_BOT_CLIENT(for_whom))
                        {
-                               if(c1 >= 0) { c2 = c3 = c4 = -1; }
-                               else if(c2 >= 0) { c1 = c3 = c4 = -1; }
-                               else { c1 = c2 = c4 = -1; }
+                               if (TeamBalance_IsTeamAllowedInternal(balance, 1))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 1);
+                               }
+                               else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 2);
+                               }
+                               else
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 3);
+                               }
                                // no further cases, we know at least 2 teams exist
                        }
                        else
                        {
-                               if(c4 >= 0) { c3 = c2 = c1 = -1; }
-                               else if(c3 >= 0) { c4 = c2 = c1 = -1; }
-                               else { c4 = c3 = c1 = -1; }
+                               if (TeamBalance_IsTeamAllowedInternal(balance, 4))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 4);
+                               }
+                               else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 3);
+                               }
+                               else
+                               {
+                                       TeamBalance_BanTeamsExcept(balance, 2);
+                               }
                                // no further cases, bots have one of the teams
                        }
                }
        }
 
-       if(!for_whom)
-               return;
+       if (!for_whom)
+       {
+               balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
+               return balance;
+       }
 
        // if player has a forced team, ONLY allow that one
-       if(for_whom.team_forced == NUM_TEAM_1 && c1 >= 0)
-               c2 = c3 = c4 = -1;
-       else if(for_whom.team_forced == NUM_TEAM_2 && c2 >= 0)
-               c1 = c3 = c4 = -1;
-       else if(for_whom.team_forced == NUM_TEAM_3 && c3 >= 0)
-               c1 = c2 = c4 = -1;
-       else if(for_whom.team_forced == NUM_TEAM_4 && c4 >= 0)
-               c1 = c2 = c3 = -1;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if (for_whom.team_forced == i &&
+                       TeamBalance_IsTeamAllowedInternal(balance, i))
+               {
+                       TeamBalance_BanTeamsExcept(balance, i);
+                       break;
+               }
+       }
+       balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
+       return balance;
 }
 
-float PlayerValue(entity p)
+void TeamBalance_Destroy(entity balance)
 {
-       return 1;
-       // FIXME: it always returns 1...
+       if (balance == NULL)
+       {
+               return;
+       }
+       for (int i = 0; i < NUM_TEAMS; ++i)
+       {
+               delete(balance.(m_team_balance_team[i]));
+       }
+       delete(balance);
 }
 
-// c1...c4 should be set to -1 (not allowed) or 0 (allowed).
-// teams that are allowed will now have their player counts stored in c1...c4
-void GetTeamCounts(entity ignore)
+int TeamBalance_GetAllowedTeams(entity balance)
 {
-       if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
+       if (balance == NULL)
+       {
+               LOG_FATAL("TeamBalance_GetAllowedTeams: Team balance entity is NULL.");
+       }
+       if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
+       {
+               LOG_FATAL("TeamBalance_GetAllowedTeams: "
+                       "Team balance entity is not initialized.");
+       }
+       int result = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if (TeamBalance_IsTeamAllowedInternal(balance, i))
+               {
+                       result |= Team_IndexToBit(i);
+               }
+       }
+       return result;
+}
+
+bool TeamBalance_IsTeamAllowed(entity balance, int index)
+{
+       if (balance == NULL)
+       {
+               LOG_FATAL("TeamBalance_IsTeamAllowed: Team balance entity is NULL.");
+       }
+       if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
+       {
+               LOG_FATAL("TeamBalance_IsTeamAllowed: "
+                       "Team balance entity is not initialized.");
+       }
+       if (!Team_IsValidIndex(index))
+       {
+               LOG_FATALF("TeamBalance_IsTeamAllowed: Team index is invalid: %f",
+                       index);
+       }
+       return TeamBalance_IsTeamAllowedInternal(balance, index);
+}
+
+void TeamBalance_GetTeamCounts(entity balance, entity ignore)
+{
+       if (balance == NULL)
+       {
+               LOG_FATAL("TeamBalance_GetTeamCounts: Team balance entity is NULL.");
+       }
+       if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
+       {
+               LOG_FATAL("TeamBalance_GetTeamCounts: "
+                       "Team balance entity is not initialized.");
+       }
+       if (MUTATOR_CALLHOOK(TeamBalance_GetTeamCounts) == true)
        {
-               if (c1 >= 0)
+               // Mutator has overriden the configuration.
+               for (int i = 1; i <= NUM_TEAMS; ++i)
                {
-                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1,
-                               num_bots_team1, lowest_human_team1, lowest_bot_team1);
-                       c1 = M_ARGV(2, float);
-                       num_bots_team1 = M_ARGV(3, float);
-                       lowest_human_team1 = M_ARGV(4, entity);
-                       lowest_bot_team1 = M_ARGV(5, entity);
-               }
-               if (c2 >= 0)
-               {
-                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2,
-                               num_bots_team2, lowest_human_team2, lowest_bot_team2);
-                       c2 = M_ARGV(2, float);
-                       num_bots_team2 = M_ARGV(3, float);
-                       lowest_human_team2 = M_ARGV(4, entity);
-                       lowest_bot_team2 = M_ARGV(5, entity);
-               }
-               if (c3 >= 0)
-               {
-                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3,
-                               num_bots_team3, lowest_human_team3, lowest_bot_team3);
-                       c3 = M_ARGV(2, float);
-                       num_bots_team3 = M_ARGV(3, float);
-                       lowest_human_team3 = M_ARGV(4, entity);
-                       lowest_bot_team3 = M_ARGV(5, entity);
-               }
-               if (c4 >= 0)
-               {
-                       MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore,
-                               c4, num_bots_team4, lowest_human_team4, lowest_bot_team4);
-                       c4 = M_ARGV(2, float);
-                       num_bots_team4 = M_ARGV(3, float);
-                       lowest_human_team4 = M_ARGV(4, entity);
-                       lowest_bot_team4 = M_ARGV(5, entity);
+                       entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
+                       if (TeamBalanceTeam_IsAllowed(team_ent))
+                       {
+                               MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore);
+                               team_ent.m_num_players = M_ARGV(2, float);
+                               team_ent.m_num_bots = M_ARGV(3, float);
+                       }
                }
        }
        else
        {
-               float value, bvalue;
-               // now count how many players are on each team already
-               float lowest_human_score1 = FLOAT_MAX;
-               float lowest_bot_score1 = FLOAT_MAX;
-               float lowest_human_score2 = FLOAT_MAX;
-               float lowest_bot_score2 = FLOAT_MAX;
-               float lowest_human_score3 = FLOAT_MAX;
-               float lowest_bot_score3 = FLOAT_MAX;
-               float lowest_human_score4 = FLOAT_MAX;
-               float lowest_bot_score4 = FLOAT_MAX;
+               // Manually count all players.
                FOREACH_CLIENT(true,
                {
-                       float t;
-                       if (IS_PLAYER(it) || it.caplayer)
+                       if (it == ignore)
                        {
-                               t = it.team;
+                               continue;
                        }
-                       else if (it.team_forced > 0)
+                       int team_num;
+                       // TODO: Reconsider when the player is truly on the team.
+                       if (IS_CLIENT(it) || (it.caplayer))
                        {
-                               t = it.team_forced; // reserve the spot
+                               team_num = it.team;
                        }
-                       else
+                       else if (Player_HasRealForcedTeam(it))
                        {
-                               continue;
+                               // Do we really need this? Probably not.
+                               team_num = Team_IndexToTeam(it.team_forced); // reserve the spot
                        }
-                       if (it == ignore)
+                       else
                        {
                                continue;
                        }
-                       value = PlayerValue(it);
-                       if (IS_BOT_CLIENT(it))
-                       {
-                               bvalue = value;
-                       }
-                       else
+                       if (!Team_IsValidTeam(team_num))
                        {
-                               bvalue = 0;
+                               continue;
                        }
-                       if (value == 0)
+                       entity team_ent = TeamBalance_GetTeam(balance, team_num);
+                       if (!TeamBalanceTeam_IsAllowed(team_ent))
                        {
                                continue;
                        }
-                       switch (t)
+                       ++team_ent.m_num_players;
+                       if (IS_BOT_CLIENT(it))
                        {
-                               case NUM_TEAM_1:
-                               {
-                                       if (c1 < 0)
-                                       {
-                                               break;
-                                       }
-                                       c1 += value;
-                                       num_bots_team1 += bvalue;
-                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
-                                       if (!bvalue)
-                                       {
-                                               if (temp_score < lowest_human_score1)
-                                               {
-                                                       lowest_human_team1 = it;
-                                                       lowest_human_score1 = temp_score;
-                                               }
-                                               break;
-                                       }
-                                       if (temp_score < lowest_bot_score1)
-                                       {
-                                               lowest_bot_team1 = it;
-                                               lowest_bot_score1 = temp_score;
-                                       }
-                                       break;
-                               }
-                               case NUM_TEAM_2:
-                               {
-                                       if (c2 < 0)
-                                       {
-                                               break;
-                                       }
-                                       c2 += value;
-                                       num_bots_team2 += bvalue;
-                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
-                                       if (!bvalue)
-                                       {
-                                               if (temp_score < lowest_human_score2)
-                                               {
-                                                       lowest_human_team2 = it;
-                                                       lowest_human_score2 = temp_score;
-                                               }
-                                               break;
-                                       }
-                                       if (temp_score < lowest_bot_score2)
-                                       {
-                                               lowest_bot_team2 = it;
-                                               lowest_bot_score2 = temp_score;
-                                       }
-                                       break;
-                               }
-                               case NUM_TEAM_3:
-                               {
-                                       if (c3 < 0)
-                                       {
-                                               break;
-                                       }
-                                       c3 += value;
-                                       num_bots_team3 += bvalue;
-                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
-                                       if (!bvalue)
-                                       {
-                                               if (temp_score < lowest_human_score3)
-                                               {
-                                                       lowest_human_team3 = it;
-                                                       lowest_human_score3 = temp_score;
-                                               }
-                                               break;
-                                       }
-                                       if (temp_score < lowest_bot_score3)
-                                       {
-                                               lowest_bot_team3 = it;
-                                               lowest_bot_score3 = temp_score;
-                                       }
-                                       break;
-                               }
-                               case NUM_TEAM_4:
-                               {
-                                       if (c4 < 0)
-                                       {
-                                               break;
-                                       }
-                                       c4 += value;
-                                       num_bots_team4 += bvalue;
-                                       float temp_score = PlayerScore_Get(it, SP_SCORE);
-                                       if (!bvalue)
-                                       {
-                                               if (temp_score < lowest_human_score4)
-                                               {
-                                                       lowest_human_team4 = it;
-                                                       lowest_human_score4 = temp_score;
-                                               }
-                                               break;
-                                       }
-                                       if (temp_score < lowest_bot_score4)
-                                       {
-                                               lowest_bot_team4 = it;
-                                               lowest_bot_score4 = temp_score;
-                                       }
-                                       break;
-                               }
+                               ++team_ent.m_num_bots;
                        }
                });
        }
 
        // if the player who has a forced team has not joined yet, reserve the spot
-       if(autocvar_g_campaign)
+       if (autocvar_g_campaign)
        {
-               switch(autocvar_g_campaign_forceteam)
+               if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
                {
-                       case 1: if(c1 == num_bots_team1) ++c1; break;
-                       case 2: if(c2 == num_bots_team2) ++c2; break;
-                       case 3: if(c3 == num_bots_team3) ++c3; break;
-                       case 4: if(c4 == num_bots_team4) ++c4; break;
+                       entity team_ent = TeamBalance_GetTeamFromIndex(balance,
+                               autocvar_g_campaign_forceteam);
+                       if (team_ent.m_num_players == team_ent.m_num_bots)
+                       {
+                               ++team_ent.m_num_players;
+                       }
                }
        }
+       balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED;
 }
 
-bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
-       bool use_score)
+int TeamBalance_GetNumberOfPlayers(entity balance, int index)
 {
-       if (!Team_IsValidNumber(team_a))
-       {
-               LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a);
-       }
-       if (!Team_IsValidNumber(team_b))
+       if (balance == NULL)
        {
-               LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b);
+               LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
+                       "Team balance entity is NULL.");
        }
-       if (team_a == team_b)
+       if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
        {
-               return false;
+               LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
+                       "TeamBalance_GetTeamCounts has not been called.");
        }
-       // we assume that CheckAllowedTeams and GetTeamCounts have already been called
-       int num_players_team_a = -1, num_players_team_b = -1;
-       int num_bots_team_a = 0, num_bots_team_b = 0;
-       float score_team_a = 0, score_team_b = 0;
-       switch (team_a)
+       if (!Team_IsValidIndex(index))
        {
-               case 1:
-               {
-                       num_players_team_a = c1;
-                       num_bots_team_a = num_bots_team1;
-                       score_team_a = team1_score;
-                       break;
-               }
-               case 2:
-               {
-                       num_players_team_a = c2;
-                       num_bots_team_a = num_bots_team2;
-                       score_team_a = team2_score;
-                       break;
-               }
-               case 3:
-               {
-                       num_players_team_a = c3;
-                       num_bots_team_a = num_bots_team3;
-                       score_team_a = team3_score;
-                       break;
-               }
-               case 4:
-               {
-                       num_players_team_a = c4;
-                       num_bots_team_a = num_bots_team4;
-                       score_team_a = team4_score;
-                       break;
-               }
+               LOG_FATALF("TeamBalance_GetNumberOfPlayers: Team index is invalid: %f",
+                       index);
        }
-       switch (team_b)
+       return balance.m_team_balance_team[index - 1].m_num_players;
+}
+
+int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
+{
+       if (balance == NULL)
        {
-               case 1:
-               {
-                       num_players_team_b = c1;
-                       num_bots_team_b = num_bots_team1;
-                       score_team_b = team1_score;
-                       break;
-               }
-               case 2:
-               {
-                       num_players_team_b = c2;
-                       num_bots_team_b = num_bots_team2;
-                       score_team_b = team2_score;
-                       break;
-               }
-               case 3:
-               {
-                       num_players_team_b = c3;
-                       num_bots_team_b = num_bots_team3;
-                       score_team_b = team3_score;
-                       break;
-               }
-               case 4:
-               {
-                       num_players_team_b = c4;
-                       num_bots_team_b = num_bots_team4;
-                       score_team_b = team4_score;
-                       break;
-               }
+               LOG_FATAL("TeamBalance_FindBestTeam: Team balance entity is NULL.");
        }
-       // invalid
-       if (num_players_team_a < 0 || num_players_team_b < 0)
+       if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
        {
-               return false;
+               LOG_FATAL("TeamBalance_FindBestTeam: "
+                       "Team balance entity is not initialized.");
        }
-       if (IS_REAL_CLIENT(player) && bots_would_leave)
+       // count how many players are in each team
+       if (ignore_player)
        {
-               num_players_team_a -= num_bots_team_a;
-               num_players_team_b -= num_bots_team_b;
+               TeamBalance_GetTeamCounts(balance, player);
        }
-       if (!use_score)
+       else
        {
-               return num_players_team_a < num_players_team_b;
+               TeamBalance_GetTeamCounts(balance, NULL);
        }
-       if (num_players_team_a < num_players_team_b)
+       int team_bits = TeamBalance_FindBestTeams(balance, player, true);
+       if (team_bits == 0)
        {
-               return true;
+               LOG_FATALF("TeamBalance_FindBestTeam: No teams available for %s\n",
+                       MapInfo_Type_ToString(MapInfo_CurrentGametype()));
        }
-       if (num_players_team_a > num_players_team_b)
+       RandomSelection_Init();
+       for (int i = 1; i <= NUM_TEAMS; ++i)
        {
-               return false;
+               if (team_bits & Team_IndexToBit(i))
+               {
+                       RandomSelection_AddFloat(i, 1, 1);
+               }
        }
-       return score_team_a < score_team_b;
+       return RandomSelection_chosen_float;
 }
 
-bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
+int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
 {
-       if (!Team_IsValidNumber(team_a))
+       if (balance == NULL)
        {
-               LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a);
+               LOG_FATAL("TeamBalance_FindBestTeams: Team balance entity is NULL.");
        }
-       if (!Team_IsValidNumber(team_b))
+       if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
        {
-               LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b);
+               LOG_FATAL("TeamBalance_FindBestTeams: "
+                       "TeamBalance_GetTeamCounts has not been called.");
        }
-       if (team_a == team_b)
+       if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true)
        {
-               return true;
+               return M_ARGV(1, float);
        }
-       // we assume that CheckAllowedTeams and GetTeamCounts have already been called
-       int num_players_team_a = -1, num_players_team_b = -1;
-       int num_bots_team_a = 0, num_bots_team_b = 0;
-       float score_team_a = 0, score_team_b = 0;
-       switch (team_a)
+       int team_bits = 0;
+       int previous_team = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
        {
-               case 1:
+               if (!TeamBalance_IsTeamAllowedInternal(balance, i))
                {
-                       num_players_team_a = c1;
-                       num_bots_team_a = num_bots_team1;
-                       score_team_a = team1_score;
-                       break;
+                       continue;
                }
-               case 2:
+               if (previous_team == 0)
                {
-                       num_players_team_a = c2;
-                       num_bots_team_a = num_bots_team2;
-                       score_team_a = team2_score;
-                       break;
+                       team_bits = Team_IndexToBit(i);
+                       previous_team = i;
+                       continue;
                }
-               case 3:
+               int compare = TeamBalance_CompareTeams(balance, i, previous_team,
+                       player, use_score);
+               if (compare == TEAMS_COMPARE_LESS)
                {
-                       num_players_team_a = c3;
-                       num_bots_team_a = num_bots_team3;
-                       score_team_a = team3_score;
-                       break;
+                       team_bits = Team_IndexToBit(i);
+                       previous_team = i;
+                       continue;
                }
-               case 4:
+               if (compare == TEAMS_COMPARE_EQUAL)
                {
-                       num_players_team_a = c4;
-                       num_bots_team_a = num_bots_team4;
-                       score_team_a = team4_score;
-                       break;
+                       team_bits |= Team_IndexToBit(i);
+                       previous_team = i;
                }
        }
-       switch (team_b)
+       return team_bits;
+}
+
+int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
+       entity player, bool use_score)
+{
+       if (balance == NULL)
        {
-               case 1:
-               {
-                       num_players_team_b = c1;
-                       num_bots_team_b = num_bots_team1;
-                       score_team_b = team1_score;
-                       break;
-               }
-               case 2:
-               {
-                       num_players_team_b = c2;
-                       num_bots_team_b = num_bots_team2;
-                       score_team_b = team2_score;
-                       break;
-               }
-               case 3:
-               {
-                       num_players_team_b = c3;
-                       num_bots_team_b = num_bots_team3;
-                       score_team_b = team3_score;
-                       break;
-               }
-               case 4:
-               {
-                       num_players_team_b = c4;
-                       num_bots_team_b = num_bots_team4;
-                       score_team_b = team4_score;
-                       break;
-               }
+               LOG_FATAL("TeamBalance_CompareTeams: Team balance entity is NULL.");
        }
-       // invalid
-       if (num_players_team_a < 0 || num_players_team_b < 0)
-               return false;
-
-       if (IS_REAL_CLIENT(player) && bots_would_leave)
+       if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
        {
-               num_players_team_a -= num_bots_team_a;
-               num_players_team_b -= num_bots_team_b;
+               LOG_FATAL("TeamBalance_CompareTeams: "
+                       "TeamBalance_GetTeamCounts has not been called.");
        }
-       if (!use_score)
+       if (!Team_IsValidIndex(team_index_a))
        {
-               return num_players_team_a == num_players_team_b;
+               LOG_FATALF("TeamBalance_CompareTeams: team_index_a is invalid: %f",
+                       team_index_a);
        }
-       if (num_players_team_a != num_players_team_b)
+       if (!Team_IsValidIndex(team_index_b))
        {
-               return false;
+               LOG_FATALF("TeamBalance_CompareTeams: team_index_b is invalid: %f",
+                       team_index_b);
+       }
+       if (team_index_a == team_index_b)
+       {
+               return TEAMS_COMPARE_EQUAL;
        }
-       return score_team_a == score_team_b;
+       entity team_a = TeamBalance_GetTeamFromIndex(balance, team_index_a);
+       entity team_b = TeamBalance_GetTeamFromIndex(balance, team_index_b);
+       return TeamBalance_CompareTeamsInternal(team_a, team_b, player, use_score);
 }
 
-int FindBestTeams(entity player, bool use_score)
+void TeamBalance_AutoBalanceBots()
 {
-       if (MUTATOR_CALLHOOK(FindBestTeams, player) == true)
-       {
-               return M_ARGV(1, float);
-       }
-       int team_bits = 0;
-       int previous_team = 0;
-       if (c1 >= 0)
+       if (!autocvar_g_balance_teams ||
+               !autocvar_g_balance_teams_prevent_imbalance)
        {
-               team_bits = BIT(0);
-               previous_team = 1;
+               return;
        }
-       if (c2 >= 0)
+       //PrintToChatAll("TeamBalance_AutoBalanceBots");
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       TeamBalance_GetTeamCounts(balance, NULL);
+       int smallest_team_index = 0;
+       int smallest_team_player_count = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
        {
-               if (previous_team == 0)
+               entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
+               if (!TeamBalanceTeam_IsAllowed(team_))
                {
-                       team_bits = BIT(1);
-                       previous_team = 2;
+                       continue;
                }
-               else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score))
+               int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
+               if (smallest_team_index == 0)
                {
-                       team_bits = BIT(1);
-                       previous_team = 2;
+                       smallest_team_index = i;
+                       smallest_team_player_count = playercount;
                }
-               else if (IsTeamEqualToTeam(2, previous_team, player, use_score))
+               else if (playercount < smallest_team_player_count)
                {
-                       team_bits |= BIT(1);
-                       previous_team = 2;
+                       smallest_team_index = i;
+                       smallest_team_player_count = playercount;
                }
        }
-       if (c3 >= 0)
+       //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
+       //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
+       entity switchable_bot = NULL;
+       int teams = BITS(NUM_TEAMS);
+       while (teams != 0)
        {
-               if (previous_team == 0)
+               int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
+                       teams);
+               if (smallest_team_index == largest_team_index)
                {
-                       team_bits = BIT(2);
-                       previous_team = 3;
+                       TeamBalance_Destroy(balance);
+                       return;
                }
-               else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score))
+               entity largest_team = TeamBalance_GetTeamFromIndex(balance,
+                       largest_team_index);
+               int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
+                       largest_team);
+               if (largest_team_player_count - smallest_team_player_count < 2)
                {
-                       team_bits = BIT(2);
-                       previous_team = 3;
+                       TeamBalance_Destroy(balance);
+                       return;
                }
-               else if (IsTeamEqualToTeam(3, previous_team, player, use_score))
+               //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
+               //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
+               switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
+                       smallest_team_index, true);
+               if (switchable_bot != NULL)
                {
-                       team_bits |= BIT(2);
-                       previous_team = 3;
+                       break;
                }
+               teams &= ~Team_IndexToBit(largest_team_index);
        }
-       if (c4 >= 0)
+       TeamBalance_Destroy(balance);
+       if (switchable_bot == NULL)
        {
-               if (previous_team == 0)
+               //PrintToChatAll("No bot found after searching through all the teams");
+               return;
+       }
+       SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
+}
+
+int TeamBalance_GetLargestTeamIndex(entity balance, int teams)
+{
+       int largest_team_index = 0;
+       int largest_team_player_count = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if (!(Team_IndexToBit(i) & teams))
                {
-                       team_bits = BIT(3);
+                       continue;
                }
-               else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score))
+               entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
+               if (!TeamBalanceTeam_IsAllowed(team_))
                {
-                       team_bits = BIT(3);
+                       continue;
                }
-               else if (IsTeamEqualToTeam(4, previous_team, player, use_score))
+               int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
+               if (largest_team_index == 0)
                {
-                       team_bits |= BIT(3);
+                       largest_team_index = i;
+                       largest_team_player_count = playercount;
+               }
+               else if (playercount > largest_team_player_count)
+               {
+                       largest_team_index = i;
+                       largest_team_player_count = playercount;
                }
        }
-       return team_bits;
-}
-
-// returns # of smallest team (1, 2, 3, 4)
-// NOTE: Assumes CheckAllowedTeams has already been called!
-int FindSmallestTeam(entity player, float ignore_player)
-{
-       // count how many players are in each team
-       if (ignore_player)
-       {
-               GetTeamCounts(player);
-       }
-       else
-       {
-               GetTeamCounts(NULL);
-       }
-       int team_bits = FindBestTeams(player, true);
-       if (team_bits == 0)
-       {
-               error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
-       }
-       RandomSelection_Init();
-       if ((team_bits & BIT(0)) != 0)
-       {
-               RandomSelection_AddFloat(1, 1, 1);
-       }
-       if ((team_bits & BIT(1)) != 0)
-       {
-               RandomSelection_AddFloat(2, 1, 1);
-       }
-       if ((team_bits & BIT(2)) != 0)
-       {
-               RandomSelection_AddFloat(3, 1, 1);
-       }
-       if ((team_bits & BIT(3)) != 0)
-       {
-               RandomSelection_AddFloat(4, 1, 1);
-       }
-       return RandomSelection_chosen_float;
+       return largest_team_index;
 }
 
-void JoinBestTeam(entity this, bool force_best_team)
+entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
+       int destination_team_index, bool is_bot)
 {
-       // don't join a team if we're not playing a team game
-       if (!teamplay)
+       if (MUTATOR_CALLHOOK(TeamBalance_GetPlayerForTeamSwitch, source_team_index,
+               destination_team_index, is_bot))
        {
-               return;
+               return M_ARGV(3, entity);
        }
-
-       // find out what teams are available
-       CheckAllowedTeams(this);
-
-       // if we don't care what team they end up on, put them on whatever team they entered as.
-       // if they're not on a valid team, then let other code put them on the smallest team
-       if (!force_best_team)
+       entity lowest_player = NULL;
+       float lowest_score = FLOAT_MAX;
+       FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
        {
-               int selected_team;
-               if ((c1 >= 0) && (this.team == NUM_TEAM_1))
-               {
-                       selected_team = this.team;
-               }
-               else if ((c2 >= 0) && (this.team == NUM_TEAM_2))
+               if (IS_BOT_CLIENT(it) != is_bot)
                {
-                       selected_team = this.team;
+                       continue;
                }
-               else if ((c3 >= 0) && (this.team == NUM_TEAM_3))
+               float temp_score = PlayerScore_Get(it, SP_SCORE);
+               if (temp_score >= lowest_score)
                {
-                       selected_team = this.team;
+                       continue;
                }
-               else if ((c4 >= 0) && (this.team == NUM_TEAM_4))
+               //PrintToChatAll(sprintf(
+               //      "Found %s with lowest score, checking allowed teams", it.netname));
+               entity balance = TeamBalance_CheckAllowedTeams(it);
+               if (TeamBalance_IsTeamAllowed(balance, source_team_index))
                {
-                       selected_team = this.team;
+                       //PrintToChatAll("Allowed");
+                       lowest_player = it;
+                       lowest_score = temp_score;
                }
                else
                {
-                       selected_team = -1;
+                       //PrintToChatAll("Not allowed");
                }
+               TeamBalance_Destroy(balance);
+       });
+       return lowest_player;
+}
 
-               if (selected_team > 0)
-               {
-                       SetPlayerTeamSimple(this, selected_team);
-                       LogTeamchange(this.playerid, this.team, 99);
-                       return;
-               }
-       }
-       // otherwise end up on the smallest team (handled below)
-       if (this.bot_forced_team)
+void LogTeamChange(float player_id, float team_number, int type)
+{
+       if (!autocvar_sv_eventlog)
        {
                return;
        }
-       int best_team = FindSmallestTeam(this, true);
-       best_team = Team_NumberToTeam(best_team);
-       if (best_team == -1)
-       {
-               error("JoinBestTeam: invalid team\n");
-       }
-       int old_team = Team_TeamToNumber(this.team);
-       TeamchangeFrags(this);
-       SetPlayerTeamSimple(this, best_team);
-       LogTeamchange(this.playerid, this.team, 2); // log auto join
-       if ((old_team != -1) && !IS_BOT_CLIENT(this))
+       if (player_id < 1)
        {
-               AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
+               return;
        }
-       KillPlayerForTeamChange(this);
+       GameLogEcho(sprintf(":team:%f:%f:%f", player_id, team_number, type));
 }
 
-void SV_ChangeTeam(entity this, float _color)
+void KillPlayerForTeamChange(entity player)
 {
-       float source_color, destination_color, source_team, destination_team;
-
-       // in normal deathmatch we can just apply the color and we're done
-       if(!teamplay)
-               SetPlayerColors(this, _color);
-
-       if(!IS_CLIENT(this))
+       if (IS_DEAD(player))
        {
-               // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
                return;
        }
-
-       if(!teamplay)
+       if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
+       {
                return;
+       }
+       Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
+               player.origin, '0 0 0');
+}
 
-       source_color = this.clientcolors & 0x0F;
-       destination_color = _color & 0x0F;
+bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
+{
+       return balance.m_team_balance_team[index - 1].m_num_players !=
+               TEAM_NOT_ALLOWED;
+}
 
-       source_team = Team_TeamToNumber(source_color + 1);
-       destination_team = Team_TeamToNumber(destination_color + 1);
+void TeamBalance_BanTeamsExcept(entity balance, int index)
+{
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if (i != index)
+               {
+                       balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
+               }
+       }
+}
 
-       if (destination_team == -1)
+entity TeamBalance_GetTeamFromIndex(entity balance, int index)
+{
+       if (!Team_IsValidIndex(index))
        {
-               return;
+               LOG_FATALF("TeamBalance_GetTeamFromIndex: Index is invalid: %f", index);
        }
+       return balance.m_team_balance_team[index - 1];
+}
 
-       CheckAllowedTeams(this);
+entity TeamBalance_GetTeam(entity balance, int team_num)
+{
+       return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
+}
 
-       if (destination_team == 1 && c1 < 0) destination_team = 4;
-       if (destination_team == 4 && c4 < 0) destination_team = 3;
-       if (destination_team == 3 && c3 < 0) destination_team = 2;
-       if (destination_team == 2 && c2 < 0) destination_team = 1;
+bool TeamBalanceTeam_IsAllowed(entity team_ent)
+{
+       return team_ent.m_num_players != TEAM_NOT_ALLOWED;
+}
 
-       // not changing teams
-       if (source_color == destination_color)
-       {
-               SetPlayerTeam(this, destination_team, source_team, true);
-               return;
-       }
+int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
+{
+       return team_ent.m_num_players;
+}
 
-       if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && CS(this).wasplayer)) {
-               Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
-               return; // changing teams is not allowed
-       }
+int TeamBalanceTeam_GetNumberOfBots(entity team_ent)
+{
+       return team_ent.m_num_bots;
+}
 
-       // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
-       if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
+int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b,
+       entity player, bool use_score)
+{
+       if (team_a == team_b)
        {
-               GetTeamCounts(this);
-               if ((BIT(destination_team - 1) & FindBestTeams(this, false)) == 0)
-               {
-                       Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
-                       return;
-               }
+               return TEAMS_COMPARE_EQUAL;
        }
-       if(IS_PLAYER(this) && source_team != destination_team)
+       if (!TeamBalanceTeam_IsAllowed(team_a) ||
+               !TeamBalanceTeam_IsAllowed(team_b))
        {
-               // reduce frags during a team change
-               TeamchangeFrags(this);
+               return TEAMS_COMPARE_INVALID;
        }
-       if (!SetPlayerTeam(this, destination_team, source_team, !IS_CLIENT(this)))
+       int num_players_team_a = team_a.m_num_players;
+       int num_players_team_b = team_b.m_num_players;
+       if (IS_REAL_CLIENT(player) && bots_would_leave)
        {
-               return;
+               num_players_team_a -= team_a.m_num_bots;
+               num_players_team_b -= team_b.m_num_bots;
        }
-       AutoBalanceBots(source_team, destination_team);
-       if (!IS_PLAYER(this) || (source_team == destination_team))
+       if (num_players_team_a < num_players_team_b)
        {
-               return;
+               return TEAMS_COMPARE_LESS;
        }
-       KillPlayerForTeamChange(this);
-}
-
-void AutoBalanceBots(int source_team, int destination_team)
-{
-       if (!Team_IsValidNumber(source_team))
+       if (num_players_team_a > num_players_team_b)
        {
-               LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team);
-               return;
+               return TEAMS_COMPARE_GREATER;
        }
-       if (!Team_IsValidNumber(destination_team))
+       if (!use_score)
        {
-               LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f",
-                       destination_team);
-               return;
+               return TEAMS_COMPARE_EQUAL;
        }
-       if (!autocvar_g_balance_teams ||
-               !autocvar_g_balance_teams_prevent_imbalance)
+       if (team_a.m_team_score < team_b.m_team_score)
        {
-               return;
+               return TEAMS_COMPARE_LESS;
        }
-       int num_players_source_team = 0;
-       int num_players_destination_team = 0;
-       entity lowest_bot_destination_team = NULL;
-       switch (source_team)
+       if (team_a.m_team_score > team_b.m_team_score)
        {
-               case 1:
-               {
-                       num_players_source_team = c1;
-                       break;
-               }
-               case 2:
-               {
-                       num_players_source_team = c2;
-                       break;
-               }
-               case 3:
-               {
-                       num_players_source_team = c3;
-                       break;
-               }
-               case 4:
-               {
-                       num_players_source_team = c4;
-                       break;
-               }
+               return TEAMS_COMPARE_GREATER;
        }
-       if (num_players_source_team < 0)
+       return TEAMS_COMPARE_EQUAL;
+}
+
+void SV_ChangeTeam(entity player, int new_color)
+{
+       if (!teamplay)
        {
-               return;
+               SetPlayerColors(player, new_color);
        }
-       switch (destination_team)
+       // TODO: Should we really bother with this?
+       if(!IS_CLIENT(player))
        {
-               case 1:
-               {
-                       num_players_destination_team = c1;
-                       lowest_bot_destination_team = lowest_bot_team1;
-                       break;
-               }
-               case 2:
-               {
-                       num_players_destination_team = c2;
-                       lowest_bot_destination_team = lowest_bot_team2;
-                       break;
-               }
-               case 3:
-               {
-                       num_players_destination_team = c3;
-                       lowest_bot_destination_team = lowest_bot_team3;
-                       break;
-               }
-               case 4:
-               {
-                       num_players_destination_team = c4;
-                       lowest_bot_destination_team = lowest_bot_team4;
-                       break;
-               }
+               // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING,
+                       player.netname);
+               return;
        }
-       if ((num_players_destination_team <= num_players_source_team) ||
-               (lowest_bot_destination_team == NULL))
+       if (!teamplay)
        {
                return;
        }
-       SetPlayerTeamSimple(lowest_bot_destination_team,
-               Team_NumberToTeam(source_team));
-       KillPlayerForTeamChange(lowest_bot_destination_team);
+       Player_SetTeamIndexChecked(player, Team_TeamToIndex((new_color & 0x0F) +
+               1));
 }
index 7c4ebe77b6c2a82a51ae2949999734d5da4022e8..33f9d02d7e6b01da87c6dd86e6217b3ab546aa95 100644 (file)
 #pragma once
 
-string cache_mutatormsg;
-string cache_lastmutatormsg;
+int autocvar_teamplay_mode;
 
-// The following variables are used for balancing. They are not updated
-// automatically. You need to call CheckAllowedTeams and GetTeamCounts to get
-// proper values.
+bool autocvar_g_changeteam_banned;
+bool autocvar_teamplay_lockonrestart;
 
-// These four have 2 different states. If they are equal to -1, it means that
-// the player can't join the team. Zero or positive value means that player can
-// join the team and means the number of players on that team.
-float c1;
-float c2;
-float c3;
-float c4;
-float num_bots_team1; ///< Number of bots in the first team.
-float num_bots_team2; ///< Number of bots in the second team.
-float num_bots_team3; ///< Number of bots in the third team.
-float num_bots_team4; ///< Number of bots in the fourth team.
-entity lowest_human_team1; ///< Human with the lowest score in the first team.
-entity lowest_human_team2; ///< Human with the lowest score in the second team.
-entity lowest_human_team3; ///< Human with the lowest score in the third team.
-entity lowest_human_team4; ///< Human with the lowest score in the fourth team.
-entity lowest_bot_team1; ///< Bot with the lowest score in the first team.
-entity lowest_bot_team2; ///< Bot with the lowest score in the second team.
-entity lowest_bot_team3; ///< Bot with the lowest score in the third team.
-entity lowest_bot_team4; ///< Bot with the lowest score in the fourth team.
+bool autocvar_g_balance_teams;
+bool autocvar_g_balance_teams_prevent_imbalance;
 
-int redowned, blueowned, yellowowned, pinkowned;
+bool lockteams;
 
-//float audit_teams_time;
+// ========================== Global teams API ================================
 
-void TeamchangeFrags(entity e);
+/// \brief Returns the global team entity at the given index.
+/// \param[in] index Index of the team.
+/// \return Global team entity at the given index.
+entity Team_GetTeamFromIndex(int index);
 
-void LogTeamchange(float player_id, float team_number, float type);
+/// \brief Returns the global team entity that corresponds to the given TEAM_NUM
+/// value.
+/// \param[in] team_num Team value. See TEAM_NUM constants.
+/// \return Global team entity that corresponds to the given TEAM_NUM value.
+entity Team_GetTeam(int team_num);
 
-void default_delayedinit(entity this);
+// ========================= Team specific API ================================
 
-void InitGameplayMode();
+/// \brief Returns the score of the team.
+/// \param[in] team_ent Team entity.
+/// \return Score of the team.
+float Team_GetTeamScore(entity team_ent);
 
-string GetClientVersionMessage(entity this);
+/// \brief Sets the score of the team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] score Score to set.
+void Team_SetTeamScore(entity team_ent, float score);
 
-string getwelcomemessage(entity this);
+/// \brief Returns the number of alive players in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of alive players in a team.
+int Team_GetNumberOfAlivePlayers(entity team_ent);
 
-void SetPlayerColors(entity player, float _color);
+/// \brief Sets the number of alive players in a team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] number Number of players to set.
+void Team_SetNumberOfAlivePlayers(entity team_ent, int number);
 
-/// \brief Kills player as a result of team change.
-/// \param[in,out] player Player to kill.
-/// \return No return.
-void KillPlayerForTeamChange(entity player);
+/// \brief Returns the number of alive teams.
+/// \return Number of alive teams.
+int Team_GetNumberOfAliveTeams();
 
-/// \brief Sets the team of the player.
+/// \brief Returns the number of control points owned by a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of control points owned by a team.
+int Team_GetNumberOfControlPoints(entity team_ent);
+
+/// \brief Sets the number of control points owned by a team.
+/// \param[in,out] team_ent Team entity.
+/// \param[in] number Number of control points to set.
+void Team_SetNumberOfControlPoints(entity team_ent, int number);
+
+/// \brief Returns the number of teams that own control points.
+/// \return Number of teams that own control points.
+int Team_GetNumberOfTeamsWithControlPoints();
+
+// ======================= Entity specific API ================================
+
+void setcolor(entity this, int clr);
+
+/// \brief Returns whether the given entity belongs to a valid team.
+/// \param[in] this Entity to check.
+/// \return True if entity belongs to a valid team, false otherwise.
+bool Entity_HasValidTeam(entity this);
+
+/// \brief Returns the team index of the given entity.
+/// \param[in] this Entity to check.
+/// \return Team index of the entity.
+int Entity_GetTeamIndex(entity this);
+
+/// \brief Returns the team entity of the given entity.
+/// \param[in] this Entity to check.
+/// \return Team entity of the given entity or NULL if the entity doesn't belong
+/// to any team.
+entity Entity_GetTeam(entity this);
+
+void SetPlayerColors(entity player, float _color);
+
+/// \brief Sets the team of the player using its index.
 /// \param[in,out] player Player to adjust.
-/// \param[in] team_num Team number to set. See TEAM_NUM constants.
+/// \param[in] index Index of the team to set.
 /// \return True if team switch was successful, false otherwise.
-bool SetPlayerTeamSimple(entity player, int team_num);
+bool Player_SetTeamIndex(entity player, int index);
+
+enum
+{
+       TEAM_CHANGE_AUTO = 2, ///< The team was selected by autobalance.
+       TEAM_CHANGE_MANUAL = 3, ///< Player has manually selected their team.
+       TEAM_CHANGE_SPECTATOR = 4 ///< Player is joining spectators. //TODO: Remove?
+};
 
 /// \brief Sets the team of the player.
 /// \param[in,out] player Player to adjust.
-/// \param[in] destination_team Team to set.
-/// \param[in] source_team Previous team of the player.
-/// \param[in] no_print Whether to print this event to players' console.
+/// \param[in] team_index Index of the team to set.
+/// \param[in] type Type of the team change. See TEAM_CHANGE constants.
 /// \return True if team switch was successful, false otherwise.
-bool SetPlayerTeam(entity player, int destination_team, int source_team,
-       bool no_print);
+bool SetPlayerTeam(entity player, int team_index, int type);
 
-// set c1...c4 to show what teams are allowed
-void CheckAllowedTeams(entity for_whom);
+/// \brief Sets the team of the player with all sanity checks.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_index Index of the team to set.
+void Player_SetTeamIndexChecked(entity player, int team_index);
 
-float PlayerValue(entity p);
+/// \brief Moves player to the specified team.
+/// \param[in,out] client Client to move.
+/// \param[in] team_index Index of the team.
+/// \param[in] type ???
+/// \return True on success, false otherwise.
+bool MoveToTeam(entity client, int team_index, int type);
 
-// c1...c4 should be set to -1 (not allowed) or 0 (allowed).
-// teams that are allowed will now have their player counts stored in c1...c4
-void GetTeamCounts(entity ignore);
+enum
+{
+       TEAM_FORCE_SPECTATOR = -1, ///< Force the player to spectator team.
+       TEAM_FORCE_DEFAULT = 0 ///< Don't force any team.
+};
 
-/// \brief Returns whether one team is smaller than the other.
-/// \param[in] team_a First team.
-/// \param[in] team_b Second team.
+/// \brief Returns whether player has real forced team. Spectator team is
+/// ignored.
 /// \param[in] player Player to check.
-/// \param[in] use_score Whether to take into account team scores.
-/// \return True if first team is smaller than the second one, false otherwise.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
-       bool use_score);
+/// \return True if player has real forced team, false otherwise.
+bool Player_HasRealForcedTeam(entity player);
 
-/// \brief Returns whether one team is equal to the other.
-/// \param[in] team_a First team.
-/// \param[in] team_b Second team.
+/// \brief Returns the index of the forced team of the given player.
+/// \param[in] player Player to check.
+/// \return Index of the forced team.
+int Player_GetForcedTeamIndex(entity player);
+
+/// \brief Sets the index of the forced team of the given player.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_index Index of the team to set.
+void Player_SetForcedTeamIndex(entity player, int team_index);
+
+/// \brief Determines the forced team of the player using current global config.
+/// \param[in,out] player Player to adjust.
+void Player_DetermineForcedTeam(entity player);
+
+// ========================= Team balance API =================================
+
+/// \brief Assigns the given player to a team that will make the game most
+/// balanced.
+/// \param[in,out] player Player to assign.
+void TeamBalance_JoinBestTeam(entity player);
+
+/// \brief Checks whether the player can join teams according to global
+/// configuration and mutator settings.
+/// \param[in] for_whom Player to check for. Pass NULL for global rules.
+/// \return Team balance entity that holds information about teams. This entity
+/// will be automatically destroyed on the next frame but you are encouraged to
+/// manually destroy it by calling TeamBalance_Destroy for performance reasons.
+entity TeamBalance_CheckAllowedTeams(entity for_whom);
+
+/// \brief Destroy the team balance entity.
+/// \param[in,out] balance Team balance entity to destroy.
+/// \note Team balance entity is allowed to be NULL.
+void TeamBalance_Destroy(entity balance);
+
+/// \brief Returns the bitmask of allowed teams.
+/// \param[in] balance Team balance entity.
+/// \return Bitmask of allowed teams.
+int TeamBalance_GetAllowedTeams(entity balance);
+
+/// \brief Returns whether the team change to the specified team is allowed.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return True if team change to the specified team is allowed, false
+/// otherwise.
+bool TeamBalance_IsTeamAllowed(entity balance, int index);
+
+/// \brief Counts the number of players and various other information about
+/// each team.
+/// \param[in,out] balance Team balance entity.
+/// \param[in] ignore Player to ignore. This is useful if you plan to switch the
+/// player's team. Pass NULL for global information.
+/// \note This function updates the internal state of the team balance entity.
+void TeamBalance_GetTeamCounts(entity balance, entity ignore);
+
+/// \brief Returns the number of players (both humans and bots) in a team.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return Number of player (both humans and bots) in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_GetNumberOfPlayers(entity balance, int index);
+
+/// \brief Finds the team that will make the game most balanced if the player
+/// joins it.
+/// \param[in] balance Team balance entity.
+/// \param[in] player Player to check.
+/// \param[in] ignore_player ???
+/// \return Index of the team that will make the game most balanced if the
+/// player joins it. If there are several equally good teams available, the
+/// function will pick a random one.
+int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player);
+
+/// \brief Returns the bitmask of the teams that will make the game most
+/// balanced if the player joins any of them.
+/// \param[in] balance Team balance entity.
 /// \param[in] player Player to check.
 /// \param[in] use_score Whether to take into account team scores.
-/// \return True if first team is equal to the second one, false otherwise.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score);
+/// \return Bitmask of the teams that will make the game most balanced if the
+/// player joins any of them.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score);
+
+/// \brief Describes the result of comparing teams.
+enum
+{
+       TEAMS_COMPARE_INVALID, ///< One or both teams are invalid.
+       TEAMS_COMPARE_LESS, ///< First team is less than the second one.
+       TEAMS_COMPARE_EQUAL, ///< Both teams are equal.
+       TEAMS_COMPARE_GREATER ///< First team the greater than the second one.
+};
 
-/// \brief Returns the bitmask of the best teams for the player to join.
+/// \brief Compares two teams for the purposes of game balance.
+/// \param[in] balance Team balance entity.
+/// \param[in] team_index_a Index of the first team.
+/// \param[in] team_index_b Index of the second team.
 /// \param[in] player Player to check.
 /// \param[in] use_score Whether to take into account team scores.
-/// \return Bitmask of the best teams for the player to join.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-int FindBestTeams(entity player, bool use_score);
+/// \return TEAMS_COMPARE value. See above.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
+       entity player, bool use_score);
 
-// returns # of smallest team (1, 2, 3, 4)
-// NOTE: Assumes CheckAllowedTeams has already been called!
-int FindSmallestTeam(entity player, float ignore_player);
+/// \brief Switches a bot from one team to another if teams are not balanced.
+void TeamBalance_AutoBalanceBots();
 
-void JoinBestTeam(entity this, bool force_best_team);
+/// \brief Returns the index of the team with most players that is contained in
+/// the given bitmask of teams.
+/// \param[in] balance Team balance entity.
+/// \param[in] teams Bitmask of teams to search in.
+/// \return Index of the team with most players.
+int TeamBalance_GetLargestTeamIndex(entity balance, int teams);
 
-/// \brief Auto balances bots in teams after the player has changed team.
-/// \param[in] source_team Previous team of the player (1, 2, 3, 4).
-/// \param[in] destination_team Current team of the player (1, 2, 3, 4).
-/// \return No return.
-/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
-/// been called.
-void AutoBalanceBots(int source_team, int destination_team);
+/// \brief Returns the player who is the most suitable for switching between
+/// the given teams.
+/// \param[in] source_team_index Index of the team to search in.
+/// \param[in] destination_team_index Index of the team to switch to.
+/// \param[in] is_bot True to search for bot, false for human.
+/// \return Player who is the most suitable for switching between the given
+/// teams or NULL if not found.
+entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
+       int destination_team_index, bool is_bot);
 
-void setcolor(entity this, int clr);
+// ============================ Internal API ==================================
+
+void LogTeamChange(float player_id, float team_number, int type);
+
+/// \brief Kills player as a result of team change.
+/// \param[in,out] player Player to kill.
+void KillPlayerForTeamChange(entity player);
+
+/// \brief Returns whether the team change to the specified team is allowed.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return True if team change to the specified team is allowed, false
+/// otherwise.
+/// \note This function bypasses all the sanity checks.
+bool TeamBalance_IsTeamAllowedInternal(entity balance, int index);
+
+/// \brief Bans team change to all teams except the given one.
+/// \param[in,out] balance Team balance entity.
+/// \param[in] index Index of the team.
+void TeamBalance_BanTeamsExcept(entity balance, int index);
+
+/// \brief Returns the team entity of the team balance entity at the given
+/// index.
+/// \param[in] balance Team balance entity.
+/// \param[in] index Index of the team.
+/// \return Team entity of the team balance entity at the given index.
+entity TeamBalance_GetTeamFromIndex(entity balance, int index);
+
+/// \brief Returns the team entity of the team balance entity that corresponds
+/// to the given TEAM_NUM value.
+/// \param[in] balance Team balance entity.
+/// \param[in] team_num Team value. See TEAM_NUM constants.
+/// \return Team entity of the team balance entity that corresponds to the given
+/// TEAM_NUM value.
+entity TeamBalance_GetTeam(entity balance, int team_num);
+
+/// \brief Returns whether the team is allowed.
+/// \param[in] team_ent Team entity.
+/// \return True if team is allowed, false otherwise.
+bool TeamBalanceTeam_IsAllowed(entity team_ent);
+
+/// \brief Returns the number of players (both humans and bots) in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of player (both humans and bots) in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent);
+
+/// \brief Returns the number of bots in a team.
+/// \param[in] team_ent Team entity.
+/// \return Number of bots in a team.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalanceTeam_GetNumberOfBots(entity team_ent);
+
+/// \brief Compares two teams for the purposes of game balance.
+/// \param[in] team_a First team.
+/// \param[in] team_b Second team.
+/// \param[in] player Player to check.
+/// \param[in] use_score Whether to take into account team scores.
+/// \return TEAMS_COMPARE value. See above.
+/// \note You need to call TeamBalance_GetTeamCounts before calling this
+/// function.
+int TeamBalance_CompareTeamsInternal(entity team_a, entity team_index_b,
+       entity player, bool use_score);
+
+/// \brief Called when the player connects or when they change their color with
+/// the "color" command.
+/// \param[in,out] player Player that requested a new color.
+/// \param[in] new_color Requested color.
+void SV_ChangeTeam(entity player, int new_color);
index 8cd7ab66c346f5bc2041c747f116c8d15b412664..0dc71ddcc01c1faa40be8307487b75a378d23067 100644 (file)
@@ -61,30 +61,31 @@ void accuracy_resend(entity e)
 //.float hit_time;
 .float fired_time;
 
-void accuracy_add(entity this, int w, int fired, int hit)
+void accuracy_add(entity this, Weapon w, int fired, int hit)
 {
        if (IS_INDEPENDENT_PLAYER(this)) return;
        entity a = CS(this).accuracy;
        if (!a) return;
        if (!hit && !fired) return;
-       if (w == WEP_Null.m_id) return;
-       w -= WEP_FIRST;
-       int b = accuracy_byte(a.accuracy_hit[w], a.accuracy_fired[w]);
-       if (hit)    a.accuracy_hit  [w] += hit;
-       if (fired)  a.accuracy_fired[w] += fired;
+       if (w == WEP_Null) return;
+       int wepid = w.m_id;
+       wepid -= WEP_FIRST;
+       int b = accuracy_byte(a.accuracy_hit[wepid], a.accuracy_fired[wepid]);
+       if (hit)    a.accuracy_hit  [wepid] += hit;
+       if (fired)  a.accuracy_fired[wepid] += fired;
 
     if (hit && STAT(HIT_TIME, a) != time) { // only run this once per frame
-        a.accuracy_cnt_hit[w] += 1;
+        a.accuracy_cnt_hit[wepid] += 1;
         STAT(HIT_TIME, a) = time;
     }
 
     if (fired && a.fired_time != time) { // only run this once per frame
-        a.accuracy_cnt_fired[w] += 1;
+        a.accuracy_cnt_fired[wepid] += 1;
         a.fired_time = time;
     }
 
-       if (b == accuracy_byte(a.accuracy_hit[w], a.accuracy_fired[w])) return; // no change
-       int sf = 1 << (w % 24);
+       if (b == accuracy_byte(a.accuracy_hit[wepid], a.accuracy_fired[wepid])) return; // no change
+       int sf = 1 << (wepid % 24);
        a.SendFlags |= sf;
        FOREACH_CLIENT(IS_SPEC(it) && it.enemy == this, { CS(it).accuracy.SendFlags |= sf; });
 }
@@ -105,12 +106,12 @@ bool accuracy_isgooddamage(entity attacker, entity targ)
        if (mutator_check == MUT_ACCADD_INVALID) return true;
 
        if (mutator_check != MUT_ACCADD_VALID) return false;
-       if (!IS_CLIENT(targ)) return false;
+       if (!IS_CLIENT(targ) || !IS_CLIENT(attacker)) return false;
 
        return true;
 }
 
 bool accuracy_canbegooddamage(entity attacker)
 {
-       return !warmup_stage;
+       return !warmup_stage && IS_CLIENT(attacker);
 }
index d24ee1cf50cf514605e9cc8d1122e854129ffa70..627698aa217c6f0bb21e4edf534cde9ee823833c 100644 (file)
@@ -25,7 +25,7 @@ void accuracy_free(entity e);
 void accuracy_resend(entity e);
 
 // update accuracy stats
-void accuracy_add(entity e, float w, float fired, float hit);
+void accuracy_add(entity e, Weapon w, float fired, float hit);
 
 // helper
 bool accuracy_isgooddamage(entity attacker, entity targ);
index 4af13e10228d101f414d61feae9ad9b4adb9b132..5912261dbf68cf6bca6a47ce8e86db480c2cd2b9 100644 (file)
@@ -266,20 +266,34 @@ void W_SwitchToOtherWeapon(entity this, .entity weaponentity)
        W_SwitchWeapon_Force(this, ww, weaponentity);
 }
 
-void W_SwitchWeapon(entity this, Weapon w, .entity weaponentity)
+bool W_SwitchWeapon(entity this, Weapon w, .entity weaponentity)
 {
        if(this.(weaponentity).m_switchweapon != w)
        {
                if(client_hasweapon(this, w, weaponentity, true, true))
+               {
                        W_SwitchWeapon_Force(this, w, weaponentity);
+                       return true;
+               }
                else
+               {
                        this.(weaponentity).selectweapon = w.m_id; // update selectweapon anyway
+                       return false;
+               }
        }
        else if(!forbidWeaponUse(this))
        {
                entity actor = this;
                w.wr_reload(w, actor, weaponentity);
        }
+
+       return true; // player already has the weapon out or needs to reload
+}
+
+void W_SwitchWeapon_TryOthers(entity this, Weapon w, .entity weaponentity)
+{
+       if(!W_SwitchWeapon(this, w, weaponentity))
+               W_NextWeaponOnImpulse(this, w.impulse, weaponentity);
 }
 
 void W_CycleWeapon(entity this, string weaponorder, float dir, .entity weaponentity)
index eea33ddb7b98e71c762a7fc09f9d7f41856c7558..dd21e6419da58f6772f3f541f18c588e782b71d1 100644 (file)
@@ -18,7 +18,8 @@ void W_SwitchWeapon_Force(Player this, Weapon w, .entity weaponentity);
 
 // perform weapon to attack (weaponstate and attack_finished check is here)
 void W_SwitchToOtherWeapon(entity this, .entity weaponentity);
-void W_SwitchWeapon(entity this, Weapon imp, .entity weaponentity);
+bool W_SwitchWeapon(entity this, Weapon imp, .entity weaponentity); // returns false if the player does not have the weapon
+void W_SwitchWeapon_TryOthers(entity this, Weapon imp, .entity weaponentity);
 
 void W_CycleWeapon(entity this, string weaponorder, float dir, .entity weaponentity);
 
index 204b5a7633669ffeac43adcf5c6da6013939d703..4ab5a43717c14e472bc30f0e5cdf6ab98d6bbbb7 100644 (file)
@@ -56,29 +56,20 @@ void weapon_defaultspawnfunc(entity this, Weapon e)
                        for (int i = 1; i < t; ++i)
                        {
                                s = argv(i);
-                               FOREACH(Weapons, it != WEP_Null, {
-                                       if(it.netname == s)
-                                       {
-                                               entity replacement = spawn();
-                                               copyentity(this, replacement);
-                                               replacement.m_isreplaced = true;
-                                               weapon_defaultspawnfunc(replacement, it);
-                                               break;
-                                       }
-                               });
+                               Weapon wep = Weapons_fromstr(s);
+                               if(wep != WEP_Null)
+                               {
+                                       entity replacement = spawn();
+                                       copyentity(this, replacement);
+                                       replacement.m_isreplaced = true;
+                                       weapon_defaultspawnfunc(replacement, wep);
+                               }
                        }
                }
                if (t >= 1) // always the case!
                {
                        s = argv(0);
-                       wpn = WEP_Null;
-                       FOREACH(Weapons, it != WEP_Null, {
-                               if(it.netname == s)
-                               {
-                                       wpn = it;
-                                       break;
-                               }
-                       });
+                       wpn = Weapons_fromstr(s);
                }
                if (wpn == WEP_Null)
                {
index 462af14d8e0ca151f366f9daa611f1caf3c00cc7..9aaabb05bfafa8075ea63466e89ab0996ffe53b5 100644 (file)
@@ -33,11 +33,9 @@ void thrown_wep_think(entity this)
                SUB_VanishOrRemove(this);
 }
 
-// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity)
+// returns amount of ammo used, or -1 for failure, or 0 for no ammo count
+float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity)
 {
-       float thisammo;
-       string s;
        Weapon info = Weapons_from(wpn);
        int ammotype = info.ammo_type;
 
@@ -84,7 +82,7 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
 
        weapon_defaultspawnfunc(wep, info);
        if(startitem_failed)
-               return string_null;
+               return -1;
        setthink(wep, thrown_wep_think);
        wep.savenextthink = wep.nextthink;
        wep.nextthink = min(wep.nextthink, time + 0.5);
@@ -93,12 +91,10 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
        //wa = W_AmmoItemCode(wpn);
        if(ammotype == RESOURCE_NONE)
        {
-               return "";
+               return 0;
        }
        else
        {
-               s = "";
-
                if(doreduce && g_weapon_stay == 2)
                {
                        // if our weapon is loaded, give its load back to the player
@@ -121,23 +117,13 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto
                        }
 
                        float ownderammo = GetResourceAmount(own, ammotype);
-                       thisammo = min(ownderammo, GetResourceAmount(wep, ammotype));
+                       float thisammo = min(ownderammo, GetResourceAmount(wep, ammotype));
                        SetResourceAmount(wep, ammotype, thisammo);
                        SetResourceAmount(own, ammotype, ownderammo - thisammo);
 
-                       switch (ammotype)
-                       {
-                               case RESOURCE_SHELLS:  s = sprintf("%s and %d shells", s, thisammo);  break;
-                               case RESOURCE_BULLETS: s = sprintf("%s and %d nails", s, thisammo);   break;
-                               case RESOURCE_ROCKETS: s = sprintf("%s and %d rockets", s, thisammo); break;
-                               case RESOURCE_CELLS:   s = sprintf("%s and %d cells", s, thisammo);   break;
-                               case RESOURCE_PLASMA:  s = sprintf("%s and %d plasma", s, thisammo);  break;
-                               case RESOURCE_FUEL:    s = sprintf("%s and %d fuel", s, thisammo);    break;
-                       }
-
-                       s = substring(s, 5, -1);
+                       return thisammo;
                }
-               return s;
+               return 0;
        }
 }
 
@@ -175,10 +161,10 @@ void W_ThrowWeapon(entity this, .entity weaponentity, vector velo, vector delta,
        STAT(WEAPONS, this) &= ~set;
 
        W_SwitchWeapon_Force(this, w_getbestweapon(this, weaponentity), weaponentity);
-       string a = W_ThrowNewWeapon(this, w.m_id, doreduce, this.origin + delta, velo, weaponentity);
+       float a = W_ThrowNewWeapon(this, w.m_id, doreduce, this.origin + delta, velo, weaponentity);
 
-       if(!a) return;
-       Send_Notification(NOTIF_ONE, this, MSG_MULTI, ITEM_WEAPON_DROP, a, w.m_id);
+       if(a < 0) return;
+       Send_Notification(NOTIF_ONE, this, MSG_MULTI, ITEM_WEAPON_DROP, w.m_id, a);
 }
 
 void SpawnThrownWeapon(entity this, vector org, Weapon wep, .entity weaponentity)
index 9ea5e5cb8e442c930f628f47f1e93ff29ef150de..20732753e4fe66f11c1fdd802df01eda300fc658 100644 (file)
@@ -6,8 +6,8 @@
 .float savenextthink;
 void thrown_wep_think(entity this);
 
-// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity);
+// returns amount of ammo used, or -1 for failure, or 0 for no ammo count
+float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity);
 
 bool W_IsWeaponThrowable(entity this, int w);
 
index b023180a138b1a9cca08235f950e8e81d633b17b..506bb43c408b847393bd13216a0e79c066130db6 100644 (file)
@@ -26,7 +26,6 @@
 void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range, int deathtype)
 {
        TC(Sound, snd);
-       float nudge = 1; // added to traceline target and subtracted from result  TOOD(divVerent): do we still need this? Doesn't the engine do this now for us?
        float oldsolid = ent.dphitcontentsmask;
        Weapon wep = DEATH_WEAPONOF(deathtype);
        if(!IS_CLIENT(ent))
@@ -57,7 +56,7 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vect
 
        // track max damage
        if (IS_PLAYER(ent) && accuracy_canbegooddamage(ent))
-               accuracy_add(ent, wep.m_id, maxdamage, 0);
+               accuracy_add(ent, wep, maxdamage, 0);
 
        if(IS_PLAYER(ent))
                W_HitPlotAnalysis(ent, wep, v_forward, v_right, v_up);
@@ -65,20 +64,20 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vect
        vector md = ent.(weaponentity).movedir;
        vector vecs = ((md.x > 0) ? md : '0 0 0');
 
-       vector dv = v_right * -vecs.y + v_up * vecs.z;
-       w_shotorg = ent.origin + ent.view_ofs + dv;
+       vector dv = v_forward * vecs.x + v_right * -vecs.y + v_up * vecs.z;
+       w_shotorg = ent.origin + ent.view_ofs;
 
        // now move the shotorg forward as much as requested if possible
        if(antilag)
        {
                if(CS(ent).antilag_debug)
-                       tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, CS(ent).antilag_debug);
+                       tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + dv, MOVE_NORMAL, ent, CS(ent).antilag_debug);
                else
-                       tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+                       tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + dv, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
        }
        else
-               tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent);
-       w_shotorg = trace_endpos - v_forward * nudge;
+               tracebox(w_shotorg, mi, ma, w_shotorg + dv, MOVE_NORMAL, ent);
+       w_shotorg = trace_endpos;
        // calculate the shotdir from the chosen shotorg
        if(W_DualWielding(ent))
                w_shotdir = s_forward;
@@ -143,7 +142,7 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vect
        }
 
        // nudge w_shotend so a trace to w_shotend hits
-       w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
+       w_shotend = w_shotend + normalize(w_shotend - w_shotorg);
        //if(w_shotend != prevend) { printf("SERVER: shotEND differs: %s - %s\n", vtos(w_shotend), vtos(prevend)); }
        //if(w_shotorg != prevorg) { printf("SERVER: shotORG differs: %s - %s\n", vtos(w_shotorg), vtos(prevorg)); }
        //if(w_shotdir != prevdir) { printf("SERVER: shotDIR differs: %s - %s\n", vtos(w_shotdir), vtos(prevdir)); }
@@ -266,7 +265,7 @@ void FireRailgunBullet (entity this, .entity weaponentity, vector start, vector
        //explosion = spawn();
 
        // Find all non-hit players the beam passed close by
-       if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id)
+       if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id) // WEAPONTODO
        {
                FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this, {
                        if(!it.railgunhit)
@@ -321,7 +320,8 @@ void FireRailgunBullet (entity this, .entity weaponentity, vector start, vector
        IL_CLEAR(g_railgunhit);
 
        // calculate hits and fired shots for hitscan
-       accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, min(bdamage, totaldmg));
+       if(this.(weaponentity))
+               accuracy_add(this, this.(weaponentity).m_weapon, 0, min(bdamage, totaldmg));
 
        trace_endpos = endpoint;
        trace_ent = endent;
@@ -336,7 +336,7 @@ void fireBullet_trace_callback(vector start, vector hit, vector end)
        fireBullet_last_hit = NULL;
 }
 
-void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects)
+void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, entity tracer_effect)
 {
        vector  end;
 
@@ -344,16 +344,11 @@ void fireBullet(entity this, .entity weaponentity, vector start, vector dir, flo
        end = start + dir * max_shot_distance;
 
        fireBullet_last_hit = NULL;
+       fireBullet_trace_callback_eff = tracer_effect;
+
        float solid_penetration_left = 1;
        float total_damage = 0;
 
-       if(tracereffects & EF_RED)
-               fireBullet_trace_callback_eff = EFFECT_RIFLE;
-       else if(tracereffects & EF_BLUE)
-               fireBullet_trace_callback_eff = EFFECT_RIFLE_WEAK;
-       else
-               fireBullet_trace_callback_eff = EFFECT_BULLET;
-
        float lag = ((IS_REAL_CLIENT(this)) ? ANTILAG_LATENCY(this) : 0);
        if(lag < 0.001)
                lag = 0;
@@ -426,7 +421,7 @@ void fireBullet(entity this, .entity weaponentity, vector start, vector dir, flo
                                // do not exceed 100%
                                float added_damage = min(damage - total_damage, damage * solid_penetration_left);
                                total_damage += damage * solid_penetration_left;
-                               accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, added_damage);
+                               accuracy_add(this, this.(weaponentity).m_weapon, 0, added_damage);
                        }
                }
 
index 9224a970cf7b8cdfacf25edb582eb5b8adb75d54..9e39ecc350aef29a8fe369baaba53ff513aa4a49 100644 (file)
@@ -57,4 +57,4 @@ void FireRailgunBullet (entity this, .entity weaponentity, vector start, vector
 entity fireBullet_trace_callback_eff;
 entity fireBullet_last_hit;
 void fireBullet_trace_callback(vector start, vector hit, vector end);
-void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects);
+void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, entity tracer_effect);
index 2017e65a760792fd3d8f0db24603729a139e4cc5..4a45b257a5db060c8b68a98facfaac9dd3ca4e5a 100644 (file)
@@ -5,6 +5,7 @@
 #include "../command/common.qh"
 #include <server/mutators/_mod.qh>
 #include "../round_handler.qh"
+#include <server/cheats.qh>
 #include <server/resources.qh>
 #include <common/t_items.qh>
 #include <common/animdecide.qh>
@@ -178,6 +179,7 @@ void CL_SpawnWeaponentity(entity actor, .entity weaponentity)
        setthink(view, CL_Weaponentity_Think);
        view.nextthink = time;
        view.viewmodelforclient = actor;
+       view.draggable = drag_undraggable;
        setcefc(view, CL_Weaponentity_CustomizeEntityForClient);
 
        wepent_link(view);
@@ -187,6 +189,7 @@ void CL_SpawnWeaponentity(entity actor, .entity weaponentity)
                entity exterior = actor.exteriorweaponentity = new(exteriorweaponentity);
                exterior.solid = SOLID_NOT;
                exterior.owner = actor;
+               exterior.draggable = drag_undraggable;
                exterior.weaponentity_fld = weaponentity;
                setorigin(exterior, '0 0 0');
                setthink(exterior, CL_ExteriorWeaponentity_Think);
@@ -289,9 +292,8 @@ bool weapon_prepareattack_check(Weapon thiswep, entity actor, .entity weaponenti
 
        if (attacktime >= 0)
        {
-               int slot = weaponslot(weaponentity);
                // don't fire if previous attack is not finished
-               if (ATTACK_FINISHED(actor, slot) > time + actor.(weaponentity).weapon_frametime * 0.5) return false;
+               if (ATTACK_FINISHED(actor, weaponentity) > time + actor.(weaponentity).weapon_frametime * 0.5) return false;
                entity this = actor.(weaponentity);
                // don't fire while changing weapon
                if (this.state != WS_READY) return false;
@@ -310,16 +312,35 @@ void weapon_prepareattack_do(entity actor, .entity weaponentity, bool secondary,
        // if the weapon hasn't been firing continuously, reset the timer
        if (attacktime >= 0)
        {
-               int slot = weaponslot(weaponentity);
-               if (ATTACK_FINISHED(actor, slot) < time - this.weapon_frametime * 1.5)
+               if (ATTACK_FINISHED(actor, weaponentity) < time - this.weapon_frametime * 1.5)
                {
-                       ATTACK_FINISHED(actor, slot) = time;
+                       ATTACK_FINISHED(actor, weaponentity) = time;
                        // dprint("resetting attack finished to ", ftos(time), "\n");
                }
-               ATTACK_FINISHED(actor, slot) = ATTACK_FINISHED(actor, slot) + attacktime * W_WeaponRateFactor(actor);
+               float arate = W_WeaponRateFactor(actor);
+               ATTACK_FINISHED(actor, weaponentity) = ATTACK_FINISHED(actor, weaponentity) + attacktime * arate;
+
+               if(autocvar_g_weaponswitch_debug_alternate && W_DualWielding(actor))
+               {
+                       int slot = weaponslot(weaponentity);
+                       for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot)
+                       {
+                               if(slot == wepslot)
+                                       continue;
+                               .entity wepent = weaponentities[wepslot];
+                               if(actor.(wepent) && actor.(wepent).m_weapon != WEP_Null)
+                               {
+                                       if(ATTACK_FINISHED(actor, wepent) > time + actor.(wepent).weapon_frametime * 0.5)
+                                               continue; // still cooling down!
+                                       if (ATTACK_FINISHED(actor, wepent) < time - actor.(wepent).weapon_frametime * 1.5)
+                                               ATTACK_FINISHED(actor, wepent) = time;
+                                       ATTACK_FINISHED(actor, wepent) = ATTACK_FINISHED(actor, wepent) + (attacktime * arate) / MAX_WEAPONSLOTS;
+                               }
+                       }
+               }
        }
        this.bulletcounter += 1;
-       // dprint("attack finished ", ftos(ATTACK_FINISHED(actor, slot)), "\n");
+       // dprint("attack finished ", ftos(ATTACK_FINISHED(actor, weaponentity)), "\n");
 }
 
 bool weapon_prepareattack(Weapon thiswep, entity actor, .entity weaponentity, bool secondary, float attacktime)
@@ -535,7 +556,7 @@ void W_WeaponFrame(Player actor, .entity weaponentity)
                                entity oldwep = this.m_weapon;
 
                                // set up weapon switch think in the future, and start drop anim
-                               if (INDEPENDENT_ATTACK_FINISHED || ATTACK_FINISHED(actor, weaponslot(weaponentity)) <= time + this.weapon_frametime * 0.5)
+                               if (INDEPENDENT_ATTACK_FINISHED || ATTACK_FINISHED(actor, weaponentity) <= time + this.weapon_frametime * 0.5)
                                {
                                        sound(actor, CH_WEAPON_SINGLE, SND_WEAPON_SWITCH, VOL_BASE, ATTN_NORM);
                                        this.state = WS_DROP;
@@ -548,7 +569,7 @@ void W_WeaponFrame(Player actor, .entity weaponentity)
 
        // LordHavoc: network timing test code
        // if (actor.button0)
-       //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(actor, slot)), " >= ", ftos(this.weapon_nextthink), "\n");
+       //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(actor, weaponentity)), " >= ", ftos(this.weapon_nextthink), "\n");
 
        Weapon w = this.m_weapon;
 
@@ -678,7 +699,7 @@ void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use, .entity weaponenti
                {
                        backtrace(sprintf(
                                "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
-                               "Please notify Samual immediately with a copy of this backtrace!\n",
+                               "Please notify the developers immediately with a copy of this backtrace!\n",
                                ammo_use,
                                wep.netname,
                                GetAmmoPicture(wep.ammo_type),
@@ -724,7 +745,7 @@ void W_ReloadedAndReady(Weapon thiswep, entity actor, .entity weaponentity, int
        // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
        // so your weapon is disabled for a few seconds without reason
 
-       // ATTACK_FINISHED(actor, slot) -= w_ent.reload_time - 1;
+       // ATTACK_FINISHED(actor, weaponentity) -= w_ent.reload_time - 1;
 
        w_ready(wpn, actor, weaponentity, PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1));
 }
@@ -797,7 +818,7 @@ void W_Reload(entity actor, .entity weaponentity, float sent_ammo_min, Sound sen
        // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
        // so your weapon is disabled for a few seconds without reason
 
-       // ATTACK_FINISHED(actor, slot) = max(time, ATTACK_FINISHED(actor, slot)) + this.reload_time + 1;
+       // ATTACK_FINISHED(actor, weaponentity) = max(time, ATTACK_FINISHED(actor, weaponentity)) + this.reload_time + 1;
 
        weapon_thinkf(actor, weaponentity, WFRAME_RELOAD, this.reload_time, W_ReloadedAndReady);
 
index eb682aa55ce7a2b2fb3880753146d9833e716a27..67589147b634f9968c2b1e6af4b906c37a3af9c1 100644 (file)
@@ -14,6 +14,7 @@ set teamplay_mode 2 // friendly fire and self damage
 set sv_vote_nospectators 1
 set g_chat_nospectators 2
 set g_warmup 1
+set g_warmup_limit 0
 set g_balance_teams 0
 set g_spawnshieldtime 0
 set g_spawn_furthest 1
index cc512e0c40dee4247caae0f4dcd920f52c6aa2e7..15086f22a698106da5983dbf0ed02c0865cd0ff4 100644 (file)
@@ -6,6 +6,7 @@ exec xonotic-server.cfg
 exec balance-overkill.cfg
 exec physicsOverkill.cfg
 exec randomitems-overkill.cfg
+if_dedicated exec help-overkill.cfg
 
 // general gameplay
 set g_overkill 1
diff --git a/scripts/cellammo.Shader b/scripts/cellammo.Shader
new file mode 100644 (file)
index 0000000..94b1c88
--- /dev/null
@@ -0,0 +1,9 @@
+cellammo\r
+{\r
+       dpreflectcube cubemaps/default/sky\r
+       dpoffsetmapping - 0.5 match8 128\r
+       {\r
+               map textures/items/cellammo\r
+               rgbgen lightingDiffuse\r
+       }\r
+}
\ No newline at end of file
diff --git a/scripts/crylink.shader b/scripts/crylink.shader
new file mode 100644 (file)
index 0000000..ab59f84
--- /dev/null
@@ -0,0 +1,9 @@
+crylink_new
+{
+       dpreflectcube cubemaps/default/sky
+       {
+               map textures/crylink_new.tga
+               rgbgen lightingDiffuse
+       }
+}
+
index 84fa2405050688836e684cb11ba502227de6542a..f87b86c8d99c4d4680e3497f194e90a28b219e8b 100644 (file)
@@ -14,3 +14,12 @@ electro_plasma_hull
        rgbGen Vertex
         }
 }
+
+electro
+{
+       dpreflectcube cubemaps/default/sky
+       {
+               map textures/electronew.tga
+               rgbgen lightingDiffuse
+       }
+}
\ No newline at end of file
index 491fd052ea30ef866ad703b80667f5d08648c96d..85f27d5c0b748f7fd6ff26849f253ea68891a50d 100644 (file)
@@ -1,11 +1,3 @@
-electro
-{
-       dpreflectcube cubemaps/default/sky
-       {
-               map textures/electro.tga
-               rgbgen lightingDiffuse
-       }
-}
 nexgun
 {
        dpreflectcube cubemaps/default/sky
diff --git a/textures/cellammoskin.jpg b/textures/cellammoskin.jpg
deleted file mode 100644 (file)
index 116ba9d..0000000
Binary files a/textures/cellammoskin.jpg and /dev/null differ
diff --git a/textures/cellammoskin_glow.jpg b/textures/cellammoskin_glow.jpg
deleted file mode 100644 (file)
index 274913c..0000000
Binary files a/textures/cellammoskin_glow.jpg and /dev/null differ
diff --git a/textures/crylink.tga b/textures/crylink.tga
deleted file mode 100644 (file)
index c07f02a..0000000
Binary files a/textures/crylink.tga and /dev/null differ
diff --git a/textures/crylink_bump.tga b/textures/crylink_bump.tga
deleted file mode 100644 (file)
index bae1984..0000000
Binary files a/textures/crylink_bump.tga and /dev/null differ
diff --git a/textures/crylink_gloss.tga b/textures/crylink_gloss.tga
deleted file mode 100644 (file)
index 84e2c17..0000000
Binary files a/textures/crylink_gloss.tga and /dev/null differ
diff --git a/textures/crylink_glow.jpg b/textures/crylink_glow.jpg
deleted file mode 100644 (file)
index de4be8e..0000000
Binary files a/textures/crylink_glow.jpg and /dev/null differ
diff --git a/textures/crylink_new.tga b/textures/crylink_new.tga
new file mode 100644 (file)
index 0000000..877f08e
Binary files /dev/null and b/textures/crylink_new.tga differ
diff --git a/textures/crylink_new_gloss.tga b/textures/crylink_new_gloss.tga
new file mode 100644 (file)
index 0000000..871a1b3
Binary files /dev/null and b/textures/crylink_new_gloss.tga differ
diff --git a/textures/crylink_new_glow.tga b/textures/crylink_new_glow.tga
new file mode 100644 (file)
index 0000000..933ddcf
Binary files /dev/null and b/textures/crylink_new_glow.tga differ
diff --git a/textures/crylink_new_norm.tga b/textures/crylink_new_norm.tga
new file mode 100644 (file)
index 0000000..026bf1d
Binary files /dev/null and b/textures/crylink_new_norm.tga differ
diff --git a/textures/crylink_new_reflect.tga b/textures/crylink_new_reflect.tga
new file mode 100644 (file)
index 0000000..70d35c0
Binary files /dev/null and b/textures/crylink_new_reflect.tga differ
diff --git a/textures/crylink_new_shirt.tga b/textures/crylink_new_shirt.tga
new file mode 100644 (file)
index 0000000..630dada
Binary files /dev/null and b/textures/crylink_new_shirt.tga differ
diff --git a/textures/crylink_reflect.tga b/textures/crylink_reflect.tga
deleted file mode 100644 (file)
index ec7857b..0000000
Binary files a/textures/crylink_reflect.tga and /dev/null differ
diff --git a/textures/crylink_shirt.tga b/textures/crylink_shirt.tga
deleted file mode 100644 (file)
index ae10ecc..0000000
Binary files a/textures/crylink_shirt.tga and /dev/null differ
diff --git a/textures/electro.tga b/textures/electro.tga
deleted file mode 100644 (file)
index adf55c2..0000000
Binary files a/textures/electro.tga and /dev/null differ
diff --git a/textures/electro_gloss.tga b/textures/electro_gloss.tga
deleted file mode 100644 (file)
index d506dcb..0000000
Binary files a/textures/electro_gloss.tga and /dev/null differ
diff --git a/textures/electro_glow.tga b/textures/electro_glow.tga
deleted file mode 100644 (file)
index 9c6a5bf..0000000
Binary files a/textures/electro_glow.tga and /dev/null differ
diff --git a/textures/electro_norm.tga b/textures/electro_norm.tga
deleted file mode 100644 (file)
index 019abc9..0000000
Binary files a/textures/electro_norm.tga and /dev/null differ
diff --git a/textures/electro_reflect.tga b/textures/electro_reflect.tga
deleted file mode 100644 (file)
index 95cc2ba..0000000
Binary files a/textures/electro_reflect.tga and /dev/null differ
diff --git a/textures/electronew.tga b/textures/electronew.tga
new file mode 100644 (file)
index 0000000..54e3706
Binary files /dev/null and b/textures/electronew.tga differ
diff --git a/textures/electronew_gloss.tga b/textures/electronew_gloss.tga
new file mode 100644 (file)
index 0000000..a59c080
Binary files /dev/null and b/textures/electronew_gloss.tga differ
diff --git a/textures/electronew_glow.tga b/textures/electronew_glow.tga
new file mode 100644 (file)
index 0000000..f539968
Binary files /dev/null and b/textures/electronew_glow.tga differ
diff --git a/textures/electronew_norm.tga b/textures/electronew_norm.tga
new file mode 100644 (file)
index 0000000..8341b47
Binary files /dev/null and b/textures/electronew_norm.tga differ
diff --git a/textures/electronew_reflect.tga b/textures/electronew_reflect.tga
new file mode 100644 (file)
index 0000000..073600b
Binary files /dev/null and b/textures/electronew_reflect.tga differ
diff --git a/textures/electronew_shirt.tga b/textures/electronew_shirt.tga
new file mode 100644 (file)
index 0000000..38d99d6
Binary files /dev/null and b/textures/electronew_shirt.tga differ
diff --git a/textures/items/cellammo.tga b/textures/items/cellammo.tga
new file mode 100644 (file)
index 0000000..50b6483
Binary files /dev/null and b/textures/items/cellammo.tga differ
diff --git a/textures/items/cellammo_gloss.tga b/textures/items/cellammo_gloss.tga
new file mode 100644 (file)
index 0000000..64b22bc
Binary files /dev/null and b/textures/items/cellammo_gloss.tga differ
diff --git a/textures/items/cellammo_glow.tga b/textures/items/cellammo_glow.tga
new file mode 100644 (file)
index 0000000..b44ff6f
Binary files /dev/null and b/textures/items/cellammo_glow.tga differ
diff --git a/textures/items/cellammo_norm.tga b/textures/items/cellammo_norm.tga
new file mode 100644 (file)
index 0000000..f7cd352
Binary files /dev/null and b/textures/items/cellammo_norm.tga differ
diff --git a/textures/items/cellammo_reflect.tga b/textures/items/cellammo_reflect.tga
new file mode 100644 (file)
index 0000000..96c0759
Binary files /dev/null and b/textures/items/cellammo_reflect.tga differ
diff --git a/vehicles.cfg b/vehicles.cfg
new file mode 100644 (file)
index 0000000..1d2f153
--- /dev/null
@@ -0,0 +1,305 @@
+set g_vehicles 1
+
+set g_vehicles_enter 0 "require pressing use key to enter a vehicle"
+set g_vehicles_enter_radius 250
+set g_vehicles_steal 1 "allow stealing enemy vehicles in teamplay modes"
+set g_vehicles_steal_show_waypoint 1 "show a waypoint above the thief"
+set g_vehicles_delayspawn 1
+set g_vehicles_delayspawn_jitter 10
+set g_vehicles_teams 1 "allow team specific vehicles"
+
+set g_vehicles_teleportable 0
+set g_vehicles_crush_dmg 70
+set g_vehicles_crush_force 50
+set g_vehicles_allow_bots 0
+set g_vehicles_exit_attempts 25
+set g_vehicles_thinkrate 0.1
+
+set g_vehicles_vortex_damagerate 0.75
+set g_vehicles_machinegun_damagerate 0.75
+set g_vehicles_rifle_damagerate 0.75
+set g_vehicles_vaporizer_damagerate 0.5
+set g_vehicles_tag_damagerate 5
+set g_vehicles_weapon_damagerate 2
+
+// {{{ #1: Bumblebee
+set g_vehicle_bumblebee 1
+set g_vehicle_bumblebee_respawntime 60
+
+set g_vehicle_bumblebee_speed_forward 350
+set g_vehicle_bumblebee_speed_strafe 350
+set g_vehicle_bumblebee_speed_up 350
+set g_vehicle_bumblebee_speed_down 350
+set g_vehicle_bumblebee_turnspeed 120
+set g_vehicle_bumblebee_pitchspeed 60
+set g_vehicle_bumblebee_pitchlimit 60
+set g_vehicle_bumblebee_friction 0.5
+set g_vehicle_bumblebee_swim 0
+
+set g_vehicle_bumblebee_energy 500
+set g_vehicle_bumblebee_energy_regen 50
+set g_vehicle_bumblebee_energy_regen_pause 1
+
+set g_vehicle_bumblebee_health 1000
+set g_vehicle_bumblebee_health_regen 65
+set g_vehicle_bumblebee_health_regen_pause 10
+
+set g_vehicle_bumblebee_shield 400
+set g_vehicle_bumblebee_shield_regen 150
+set g_vehicle_bumblebee_shield_regen_pause 0.75
+
+set g_vehicle_bumblebee_cannon_ammo 100
+set g_vehicle_bumblebee_cannon_ammo_regen 100
+set g_vehicle_bumblebee_cannon_ammo_regen_pause 1
+
+set g_vehicle_bumblebee_cannon_lock 1
+
+set g_vehicle_bumblebee_cannon_turnspeed 260
+set g_vehicle_bumblebee_cannon_pitchlimit_down 60
+set g_vehicle_bumblebee_cannon_pitchlimit_up 60
+set g_vehicle_bumblebee_cannon_turnlimit_in 20
+set g_vehicle_bumblebee_cannon_turnlimit_out 80
+
+
+set g_vehicle_bumblebee_raygun_turnspeed 180
+set g_vehicle_bumblebee_raygun_pitchlimit_down 20
+set g_vehicle_bumblebee_raygun_pitchlimit_up 5
+set g_vehicle_bumblebee_raygun_turnlimit_sides 35
+
+set g_vehicle_bumblebee_raygun 0
+set g_vehicle_bumblebee_raygun_range 2048
+set g_vehicle_bumblebee_raygun_dps 250
+set g_vehicle_bumblebee_raygun_aps 100
+set g_vehicle_bumblebee_raygun_fps 100
+
+set g_vehicle_bumblebee_healgun_hps 150
+set g_vehicle_bumblebee_healgun_hmax 100
+set g_vehicle_bumblebee_healgun_aps 75
+set g_vehicle_bumblebee_healgun_amax 100
+set g_vehicle_bumblebee_healgun_sps 100
+set g_vehicle_bumblebee_healgun_locktime 2.5
+
+set g_vehicle_bumblebee_blowup_radius 500
+set g_vehicle_bumblebee_blowup_coredamage 500
+set g_vehicle_bumblebee_blowup_edgedamage 100
+set g_vehicle_bumblebee_blowup_forceintensity 600
+set g_vehicle_bumblebee_bouncepain "1 100 200"
+
+set g_vehicle_bumblebee_cannon_cost 2
+set g_vehicle_bumblebee_cannon_damage 60
+set g_vehicle_bumblebee_cannon_radius 225
+set g_vehicle_bumblebee_cannon_refire 0.2
+set g_vehicle_bumblebee_cannon_speed 20000
+set g_vehicle_bumblebee_cannon_spread 0
+set g_vehicle_bumblebee_cannon_force -35
+// }}}
+// {{{ #2: Racer
+set g_vehicle_racer 1
+set g_vehicle_racer_respawntime 35
+
+set g_vehicle_racer_thinkrate 0.05 // TODO: any higher causes it to sink in liquids
+
+set g_vehicle_racer_speed_afterburn 3000
+set g_vehicle_racer_afterburn_cost 130 "energy consumed per second"
+
+set g_vehicle_racer_waterburn_cost 5
+set g_vehicle_racer_waterburn_speed 750
+
+set g_vehicle_racer_water_speed_forward 600
+set g_vehicle_racer_water_speed_strafe 600
+
+set g_vehicle_racer_pitchlimit 30
+
+set g_vehicle_racer_water_downforce 0.03
+set g_vehicle_racer_water_upforcedamper 15
+
+set g_vehicle_racer_anglestabilizer 1.75
+set g_vehicle_racer_downforce 0.01
+
+set g_vehicle_racer_speed_forward 650
+set g_vehicle_racer_speed_strafe 650
+set g_vehicle_racer_springlength 90
+set g_vehicle_racer_upforcedamper 2
+set g_vehicle_racer_friction 0.45
+
+set g_vehicle_racer_water_time 5
+
+set g_vehicle_racer_hovertype 0 "0 = hover, otherwise = maglev"
+set g_vehicle_racer_hoverpower 8000 "this is multiplied by 4 for the 4 engines"
+
+set g_vehicle_racer_turnroll 30
+set g_vehicle_racer_turnspeed 220
+set g_vehicle_racer_pitchspeed 125
+
+set g_vehicle_racer_energy 100
+set g_vehicle_racer_energy_regen 90
+set g_vehicle_racer_energy_regen_pause 0.35
+
+set g_vehicle_racer_health 200
+set g_vehicle_racer_health_regen 0
+set g_vehicle_racer_health_regen_pause 0
+
+set g_vehicle_racer_shield 100
+set g_vehicle_racer_shield_regen 30
+set g_vehicle_racer_shield_regen_pause 1
+
+set g_vehicle_racer_rocket_locktarget 1
+set g_vehicle_racer_rocket_locking_time 0.35
+set g_vehicle_racer_rocket_locking_releasetime 0.5
+set g_vehicle_racer_rocket_locked_time 4
+
+set g_vehicle_racer_blowup_radius 250
+set g_vehicle_racer_blowup_coredamage 250
+set g_vehicle_racer_blowup_edgedamage 15
+set g_vehicle_racer_blowup_forceintensity 250
+
+set g_vehicle_racer_bouncefactor 0.25 "factor of old velocity to keep after collision"
+set g_vehicle_racer_bouncestop 0 "if not 0, new velocity after bounce is 0 if new velocity is smaller than this"
+set g_vehicle_racer_bouncepain "200 0.15 150" "minspeed_for_pain speedchange_to_pain_factor max_damage"
+
+set g_vehicle_racer_cannon_cost 1.5
+set g_vehicle_racer_cannon_damage 15
+set g_vehicle_racer_cannon_radius 100
+set g_vehicle_racer_cannon_refire 0.05
+set g_vehicle_racer_cannon_speed 15000
+set g_vehicle_racer_cannon_spread 0.0125
+set g_vehicle_racer_cannon_force 50
+
+set g_vehicle_racer_rocket_accel 1600
+set g_vehicle_racer_rocket_damage 100
+set g_vehicle_racer_rocket_radius 125
+set g_vehicle_racer_rocket_force 350
+set g_vehicle_racer_rocket_speed 900
+set g_vehicle_racer_rocket_turnrate 0.2
+set g_vehicle_racer_rocket_refire 3
+
+set g_vehicle_racer_rocket_climbspeed 1600
+set g_vehicle_racer_rocket_locked_maxangle 1.8
+// }}}
+// {{{ #3: Raptor
+set g_vehicle_raptor 1
+set g_vehicle_raptor_respawntime 40
+
+set g_vehicle_raptor_takeofftime 1.5
+
+set g_vehicle_raptor_movestyle 1 "0: move relative to player angles, 1: ignore aiming for up/down movement"
+set g_vehicle_raptor_turnspeed 200
+set g_vehicle_raptor_pitchspeed 50
+set g_vehicle_raptor_pitchlimit 45
+
+set g_vehicle_raptor_speed_forward 1700
+set g_vehicle_raptor_speed_strafe 2200
+set g_vehicle_raptor_speed_up 2300
+set g_vehicle_raptor_speed_down 2000
+set g_vehicle_raptor_friction 2
+
+set g_vehicle_raptor_swim 0
+
+set g_vehicle_raptor_cannon_turnspeed 120
+set g_vehicle_raptor_cannon_turnlimit 20
+set g_vehicle_raptor_cannon_pitchlimit_up 12
+set g_vehicle_raptor_cannon_pitchlimit_down 32
+
+set g_vehicle_raptor_cannon_locktarget 1
+set g_vehicle_raptor_cannon_locking_time 0.2
+set g_vehicle_raptor_cannon_locking_releasetime 0.45
+set g_vehicle_raptor_cannon_locked_time 1
+set g_vehicle_raptor_cannon_predicttarget 1
+
+set g_vehicle_raptor_energy 100
+set g_vehicle_raptor_energy_regen 25
+set g_vehicle_raptor_energy_regen_pause 0.25
+
+set g_vehicle_raptor_health 250
+set g_vehicle_raptor_health_regen 0
+set g_vehicle_raptor_health_regen_pause 0
+
+set g_vehicle_raptor_shield 200
+set g_vehicle_raptor_shield_regen 25
+set g_vehicle_raptor_shield_regen_pause 1.5
+
+set g_vehicle_raptor_bouncefactor 0.2
+set g_vehicle_raptor_bouncestop 0
+set g_vehicle_raptor_bouncepain "1 4 1000"
+
+set g_vehicle_raptor_cannon_cost 1
+set g_vehicle_raptor_cannon_damage 10
+set g_vehicle_raptor_cannon_radius 60
+set g_vehicle_raptor_cannon_refire 0.03
+set g_vehicle_raptor_cannon_speed 24000
+set g_vehicle_raptor_cannon_spread 0.01
+set g_vehicle_raptor_cannon_force 25
+
+set g_vehicle_raptor_bomblets 8
+set g_vehicle_raptor_bomblet_alt 750
+set g_vehicle_raptor_bomblet_time 0.5
+set g_vehicle_raptor_bomblet_damage 55
+set g_vehicle_raptor_bomblet_spread 0.4
+set g_vehicle_raptor_bomblet_edgedamage 25
+set g_vehicle_raptor_bomblet_radius 350
+set g_vehicle_raptor_bomblet_force 150
+set g_vehicle_raptor_bomblet_explode_delay 0.4
+
+set g_vehicle_raptor_bombs_refire 5
+
+set g_vehicle_raptor_flare_refire 5
+set g_vehicle_raptor_flare_lifetime 10
+set g_vehicle_raptor_flare_chase 0.9
+set g_vehicle_raptor_flare_range 2000
+// }}}
+// {{{ #4: Spiderbot
+set g_vehicle_spiderbot 1
+set g_vehicle_spiderbot_respawntime 45
+
+set g_vehicle_spiderbot_speed_stop 50
+set g_vehicle_spiderbot_speed_strafe 400
+set g_vehicle_spiderbot_speed_walk 500
+set g_vehicle_spiderbot_speed_run 700
+set g_vehicle_spiderbot_turnspeed 90
+set g_vehicle_spiderbot_turnspeed_strafe 300
+set g_vehicle_spiderbot_movement_inertia 0.15
+
+set g_vehicle_spiderbot_springlength 150
+set g_vehicle_spiderbot_springup 20
+set g_vehicle_spiderbot_springblend 0.1
+set g_vehicle_spiderbot_tiltlimit 90
+
+set g_vehicle_spiderbot_head_pitchlimit_down -20
+set g_vehicle_spiderbot_head_pitchlimit_up 30
+set g_vehicle_spiderbot_head_turnlimit 90
+set g_vehicle_spiderbot_head_turnspeed 110
+
+set g_vehicle_spiderbot_health 800
+set g_vehicle_spiderbot_health_regen 10
+set g_vehicle_spiderbot_health_regen_pause 5
+
+set g_vehicle_spiderbot_shield 200
+set g_vehicle_spiderbot_shield_regen 25
+set g_vehicle_spiderbot_shield_regen_pause 0.35
+
+set g_vehicle_spiderbot_bouncepain "0 0 0" "minspeed_for_pain speedchange_to_pain_factor max_damage"
+
+set g_vehicle_spiderbot_minigun_damage 16
+set g_vehicle_spiderbot_minigun_refire 0.06
+set g_vehicle_spiderbot_minigun_spread 0.012
+set g_vehicle_spiderbot_minigun_ammo_cost 1
+set g_vehicle_spiderbot_minigun_ammo_max 100
+set g_vehicle_spiderbot_minigun_ammo_regen 40
+set g_vehicle_spiderbot_minigun_ammo_regen_pause 1
+set g_vehicle_spiderbot_minigun_force 9
+set g_vehicle_spiderbot_minigun_solidpenetration 32
+
+set g_vehicle_spiderbot_rocket_damage 50
+set g_vehicle_spiderbot_rocket_force 150
+set g_vehicle_spiderbot_rocket_radius 250
+set g_vehicle_spiderbot_rocket_speed 3500
+set g_vehicle_spiderbot_rocket_spread 0.05
+set g_vehicle_spiderbot_rocket_refire 0.1
+// volley
+set g_vehicle_spiderbot_rocket_refire2 0.025
+set g_vehicle_spiderbot_rocket_reload 4
+set g_vehicle_spiderbot_rocket_health 100
+set g_vehicle_spiderbot_rocket_noise 0.2
+set g_vehicle_spiderbot_rocket_turnrate 0.25
+set g_vehicle_spiderbot_rocket_lifetime 20
+// }}}
index 043ff25553bd894cf481493212c944ef329abca0..3f9baf571bb2f56366bbeb2c511110ec486a1042 100644 (file)
@@ -197,6 +197,8 @@ seta cl_hitsound_min_pitch 0.75 "minimum pitch of hit sound"
 seta cl_hitsound_max_pitch 1.5 "maximum pitch of hit sound"
 seta cl_hitsound_nom_damage 25 "damage amount at which hitsound bases pitch off"
 
+seta cl_eventchase_spectated_change 1 "camera goes into 3rd person mode for a moment when changing spectated player"
+seta cl_eventchase_spectated_change_time 1 "how much time the effect lasts when changing spectated player"
 seta cl_eventchase_death 1 "camera goes into 3rd person mode when the player is dead; set to 2 to active the effect only when the corpse doesn't move anymore"
 seta cl_eventchase_frozen 0 "camera goes into 3rd person mode when the player is frozen"
 seta cl_eventchase_nexball 1 "camera goes into 3rd person mode when in nexball game-mode"
@@ -229,11 +231,17 @@ cl_movement 1
 cl_movement_track_canjump 0
 cl_stairsmoothspeed 200
 
-alias g_waypointeditor_spawn "impulse 103"
-alias g_waypointeditor_remove "impulse 104"
-alias g_waypointeditor_relinkall "impulse 105"
-alias g_waypointeditor_saveall "impulse 106"
-alias g_waypointeditor_unreachable "impulse 107"
+alias g_waypointeditor_spawn         "wpeditor spawn"
+alias g_waypointeditor_remove        "wpeditor remove"
+alias g_waypointeditor_relinkall     "wpeditor relinkall"
+alias g_waypointeditor_saveall       "wpeditor saveall"
+alias g_waypointeditor_unreachable   "wpeditor unreachable"
+
+alias navwaypoint_relink        g_waypointeditor_spawn
+alias navwaypoint_remove        g_waypointeditor_remove
+alias navwaypoint_save          g_waypointeditor_relinkall
+alias navwaypoint_spawn         g_waypointeditor_saveall
+alias navwaypoint_unreachable   g_waypointeditor_unreachable
 
 seta menu_sandbox_spawn_model ""
 seta menu_sandbox_attach_bone ""
@@ -384,6 +392,7 @@ set g_waypointsprite_spam 0 "Debugging feature. Set to 10 and load courtfun in r
 set g_waypointsprite_timealphaexponent 1
 seta g_waypointsprite_turrets 1 "disable turret waypoints"
 seta g_waypointsprite_turrets_maxdist 5000 "max distance for turret waypoints"
+seta g_waypointsprite_turrets_text 0 "show the turret's name in the waypoint"
 seta g_waypointsprite_uppercase 1
 seta g_waypointsprite_text 0 "Always show text instead of icons, setting this to 0 will still use text if the icon is unavailable"
 seta g_waypointsprite_iconsize 32
@@ -643,6 +652,8 @@ seta cl_jetpack_jump 1 "Activate jetpack by pressing jump in the air. 0 = Disabl
 seta cl_race_cptimes_showself 1 "Always show your own times as well as the current best on checkpoints in Race/CTS"
 seta cl_race_cptimes_onlyself 0 "Only show your own times on checkpoints in Race/CTS"
 
+seta cl_cts_noautoswitch 0 "Prevent forced switching to new weapons in CTS"
+
 set cl_stripcolorcodes 0       "experimental feature (notes: strips ALL color codes from messages!)"
 
 // Demo camera
@@ -716,6 +727,8 @@ seta cl_forcemyplayercolors 0 "set to the color value (encoding is same as _cl_c
 seta cl_movement_errorcompensation 1 "try to compensate for prediction errors and reduce perceived lag"
 seta cl_movement_intermissionrunning 0 "keep velocity after the match ends, players may appear to continue running while stationary"
 
+seta cl_viewmodel_alpha 0 "Maximum transparency of the view model, set to 0 to disable"
+
 set debugdraw 0
 set debugdraw_filter ""
 set debugdraw_filterout ""
@@ -772,17 +785,14 @@ r_cullentities_trace 0
 r_shadow_glossexact 1
 r_shadow_glossintensity 1
 
-// use fake light if map has no lightmaps
-r_fakelight 1
+// use slightly better lighting than r_fullbright if map has no lightmaps, and for fullbrightplayers
+r_fullbright_directed 1
 
 r_water_hideplayer 1 // hide your own feet/player model in refraction views, this way you don't see half of your body under water
 r_water_refractdistort 0.019
 
 set cl_rainsnow_maxdrawdist 2048
 
-// equalize looks better than fullbright
-r_equalize_entities_fullbright 1
-
 // safe font defaults
 r_font_hinting 1
 r_font_disable_freetype 0
index 078fbed74c0bc58f76fb6ac4200b4b5de61406e7..3a01784c7c5d4bf6c5451fbda664ad0d44f83799 100644 (file)
@@ -145,3 +145,6 @@ exec commands.cfg
 // Change g_start_delay based upon if the server is local or not.
 if_client set g_start_delay 0  "delay before the game starts, so everyone can join; recommended to set this to like 15 on a public server"
 if_dedicated set g_start_delay 15      "delay before the game starts, so everyone can join; recommended to set this to like 15 on a public server"
+
+// this should be execed only once even on ruleset-votable servers, otherwise the tips would always start from 0
+if_dedicated exec help.cfg
index 21a94e340f1f643d06b4b86c9fbe3c504679a8cb..956fb215548c55d20e9a62bd27cc2d0e31942901 100644 (file)
@@ -10,7 +10,7 @@ set sv_autotaunt 1 "allow autotaunts on the server"
 // server settings
 hostname "Xonotic $g_xonoticversion Server"
 set sv_mapchange_delay 5
-set minplayers 0 "number of players playing at the same time (if not enough real players are there the remaining slots are filled with bots)"
+set minplayers 0 "fill server with bots to reach this number of players (if bot_number is not enough)"
 
 // restart server if all players hit "ready"-button
 set sv_ready_restart 0 "allow a map to be restarted once all players pressed the \"ready\" button"
@@ -25,7 +25,7 @@ set g_maxplayers_spectator_blocktime 5        "if the players voted for the \"nospectat
 
 // tournament mod
 set g_warmup 0 "split the game into a warmup- and match-stage"
-set g_warmup_limit 0   "limit warmup-stage to this time (in seconds); if set to -1 the warmup-stage is not affected by any timelimit, if set to 0 the usual timelimit also affects warmup-stage"
+set g_warmup_limit 180 "limit warmup-stage to this time (in seconds); if set to -1 the warmup-stage is not affected by any timelimit, if set to 0 the usual timelimit also affects warmup-stage"
 set g_warmup_allow_timeout 0   "allow calling timeouts in the warmup-stage (if sv_timeout is set to 1)"
 set g_warmup_allguns 1 "provide more weapons on start while in warmup: 0 = normal start weapons, 1 = all guns available on the map, 2 = all normal weapons"
 set g_warmup_majority_factor 0.8 "minimum percentage of players ready needed for warmup to end"
@@ -80,8 +80,6 @@ set sv_track_canjump 0 "track if the player released the jump key between 2 jump
 set sv_jumpvelocity_crouch 0 "jump height while crouching, set to 0 to use regular jump height"
 
 set sv_precacheplayermodels 1
-set sv_precacheweapons 0
-set sv_precacheitems 0
 set sv_spectator_speed_multiplier 1.5
 set sv_spectator_speed_multiplier_min 1
 set sv_spectator_speed_multiplier_max 5
@@ -243,8 +241,6 @@ set timelimit_overtimes 0 "how many overtimes to add at max"
 set timelimit_suddendeath 5 "number of minutes suddendeath mode lasts after all overtimes were added and still no winner was found"
 
 // common team values
-set g_tdm 0 "Team Deathmatch: the team who kills their opponents most often wins"
-set g_tdm_on_dm_maps 0 "when this is set, all DM maps automatically support TDM"
 
 set teamplay_mode 4 "default teamplay setting in team games. 1 = no friendly fire, self damage. 2 = friendly fire and self damage enabled. 3 = no friendly fire, but self damage enabled. 4 = obey the cvars g_mirrordamage*, g_friendlyfire* and g_teamdamage*"
 set g_mirrordamage 0.7              "for teamplay_mode 4: mirror damage factor"
@@ -258,9 +254,7 @@ set g_teamdamage_resetspeed 20      "for teamplay_mode 4: how fast player's team
 
 set g_balance_teams 1  "automatically balance out players entering instead of asking them for their preferred team"
 set g_balance_teams_prevent_imbalance  1       "prevent players from changing to larger teams"
-set g_balance_teams_scorefactor 0.25 "at the end of the game, take score into account instead of team size by this amount (beware: values over 0.5 mean that a x:0 score imbalance will cause ALL new players to prefer the losing team at the end, despite numbers)"
 set g_changeteam_banned 0      "not allowed to change team"
-set g_changeteam_fragtransfer 0        "% of frags you get to keep when you change teams (rounded down)"
 
 set sv_teamnagger 1 "enable a nag message when the teams are unbalanced"
 
@@ -343,7 +337,7 @@ set g_maplist_votable_nodetail 0    "nodetail only shows total count instead of all
 set g_maplist_votable_abstain 0        "when 1, you can abstain from your vote"
 set g_maplist_votable_screenshot_dir "maps levelshots" "where to look for map screenshots"
 
-set sv_vote_gametype 1 "show a vote screen for gametypes before map vote screen"
+set sv_vote_gametype 0 "show a vote screen for gametypes before map vote screen"
 set sv_vote_gametype_keeptwotime 10 "show only 2 options after this amount of time during gametype vote screen"
 set sv_vote_gametype_options "dm tdm ctf" "Keep the identifiers short, otherwise you'll run into issues with too long alias names (max is 31 characters) when using sv_vote_gametype_hook_*"
 set sv_vote_gametype_timeout 20
@@ -374,6 +368,7 @@ set sv_itemstime 1 "enable networking of time left until respawn for items such
 
 set g_ban_default_bantime 5400 "90 minutes"
 set g_ban_default_masksize 3   "masksize 0 means banning by UID only, 1 means banning by /8 (IPv6: /32) network, 2 means banning by /16 (IPv6: /48) network, 3 means banning by /24 (IPv6: /56) network, 4 means banning by single IP (IPv6: /64 network)"
+set g_ban_telluser 1 "notify the banned player about it when they try to join"
 set g_banned_list ""   "format: IP remainingtime IP remainingtime ..."
 set g_banned_list_idmode "1"   "when set, the IP banning system always uses the ID over the IP address (so a user in a banned IP range can connect if they have a valid signed ID)"
 
@@ -440,6 +435,7 @@ set g_jetpack 0 "Jetpack mutator"
 set g_hitplots 0 "when set to 1, hitplots are stored by the server to provide a means of proving that a triggerbot was used"
 set g_hitplots_individuals "" "the individuals, by IP, that should have their hitplots recorded"
 
+// set it to 1 to "fix bot moveto command and routing... now all bots can get to their seats" (Nexuiz repo, commit 2c9873e6)
 set bot_navigation_ignoreplayers 0 // FIXME remove this once the issue is solved
 set bot_sound_monopoly 0 "when enabled, only bots can make any noise"
 
@@ -483,6 +479,7 @@ sv_gameplayfix_gravityunaffectedbyticrate 1
 sv_gameplayfix_nogravityonground 1
 
 set sv_q3acompat_machineshotgunswap 0 "shorthand for swapping machinegun and shotgun (for Q3A map compatibility in mapinfo files)"
+set sv_vq3compat 0 "toggle for some compatibility hacks (for VQ3 and CPM map compatibility in mapinfo files)"
 
 set g_movement_highspeed 1 "movement speed modification factor (only changes movement when above maxspeed)"
 
@@ -543,11 +540,13 @@ set g_mod_config  "" "Current config mod name"
 exec balance-xonotic.cfg
 exec physicsX.cfg
 exec turrets.cfg
+exec vehicles.cfg
 exec gamemodes-server.cfg
 exec mutators.cfg
 exec monsters.cfg
 exec minigames.cfg
 exec physics.cfg
+if_dedicated exec help-xonotic.cfg
 
 set sv_join_notices ""
 set sv_join_notices_time 15