From: Mario Date: Sat, 20 Dec 2014 04:48:17 +0000 (+1100) Subject: Merge branch 'master' into Mario/notifications X-Git-Tag: xonotic-v0.8.0~56^2~1 X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=a6a3b0cebd6928e3678ee4e97de4093f1b61c58b Merge branch 'master' into Mario/notifications Conflicts: qcsrc/common/notifications.qh qcsrc/server/command/sv_cmd.qc qcsrc/server/mutators/gamemode_assault.qc qcsrc/server/teamplay.qc --- a6a3b0cebd6928e3678ee4e97de4093f1b61c58b diff --cc qcsrc/common/notifications.qh index ef1e02285,a17f703ff..0bfd2e5f3 --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@@ -339,9 -339,7 +339,12 @@@ void Send_Notification_WOCOVA MULTITEAM_INFO##teams(default,prefix,strnum,flnum,args,hudargs,icon,normal,gentle) #define MSG_INFO_NOTIFICATIONS \ + MSG_INFO_NOTIF(2, INFO_CHAT_NOSPECTATORS, 0, 0, "", "", "", _("^F4NOTE: ^BGSpectator chat is not sent to players during the match"), "") \ + MSG_INFO_NOTIF(2, INFO_COINTOSS, 1, 0, "s1", "", "", _("^F2Throwing coin... Result: %s^F2!"), "") \ + MSG_INFO_NOTIF(1, INFO_JETPACK_NOFUEL, 0, 0, "", "", "", _("^BGYou don't have any fuel for the ^F1Jetpack"), "") \ + MSG_INFO_NOTIF(2, INFO_SUPERSPEC_MISSING_UID, 0, 0, "", "", "", _("^F2You lack a UID, superspec options will not be saved/restored"), "") \ ++ MSG_INFO_NOTIF(1, INFO_CA_JOIN_LATE, 0, 0, "", "", "", _("^F1Round already started, you will join the game in the next round"), "") \ ++ MSG_INFO_NOTIF(1, INFO_CA_LEAVE, 0, 0, "", "", "", _("^F2You will spectate in the next round"), "") \ MULTITEAM_INFO(1, INFO_CTF_CAPTURE_, 2, 1, 0, "s1", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag"), "") \ MULTITEAM_INFO(1, INFO_CTF_CAPTURE_BROKEN_, 2, 2, 2, "s1 f1p2dec s2 f2p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%s^BG seconds, breaking ^BG%s^BG's previous record of ^F2%s^BG seconds"), "") \ MULTITEAM_INFO(1, INFO_CTF_CAPTURE_TIME_, 2, 1, 1, "s1 f1p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%s^BG seconds"), "") \ @@@ -421,6 -438,9 +443,7 @@@ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_WAKI_ROCKET, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 couldn't find shelter from a Racer rocket%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VOID, 2, 1, "s1 s2loc spree_lost", "s1", "notify_void", _("^BG%s^K1 was in the wrong place%s%s"), "") \ MULTITEAM_INFO(1, INFO_DEATH_TEAMKILL_, 4, 3, 1, "s1 s2 s3loc spree_end", "s2 s1", "notify_teamkill_%s", _("^BG%s^K1 was betrayed by ^BG%s^K1%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_CA_JOIN_LATE, 0, 0, "", "", "", _("^F1Round already started, you will join the game in the next round"), "") \ - MSG_INFO_NOTIF(1, INFO_CA_LEAVE, 0, 0, "", "", "", _("^F2You will spectate in the next round"), "") \ + MSG_INFO_NOTIF(1, INFO_DOMINATION_CAPTURE_TIME, 2, 2, "s1 s2 f1 f2", "", "", _("^BG%s^BG%s^BG (%s points every %s seconds)"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_FREEZE, 2, 0, "s1 s2", "", "", _("^BG%s^K1 was frozen by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED, 2, 0, "s1 s2", "", "", _("^BG%s^K3 was revived by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_FALL, 1, 0, "s1", "", "", _("^BG%s^K3 was revived by falling"), "") \ @@@ -492,11 -528,10 +531,11 @@@ MSG_INFO_NOTIF(1, INFO_WEAPON_HOOK_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhook", _("^BG%s%s^K1 was caught in ^BG%s^K1's Hook gravity bomb%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_KLEINBOTTLE_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapontuba", _("^BG%s%s^K1 died of ^BG%s^K1's great playing on the @!#%%'n Klein Bottle%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_KLEINBOTTLE_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weapontuba", _("^BG%s^K1 hurt their own ears with the @!#%%'n Klein Bottle%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_WEAPON_LASER_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponlaser", _("^BG%s%s^K1 was shot to death by ^BG%s^K1's Laser%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_WEAPON_LASER_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weaponlaser", _("^BG%s^K1 shot themself to hell with their Laser%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_MACHINEGUN_MURDER_SNIPE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was sniped by ^BG%s^K1's Machine Gun%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_MACHINEGUN_MURDER_SPRAY, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was riddled full of holes by ^BG%s^K1's Machine Gun%s%s"), "") \ ++ MSG_INFO_NOTIF(1, INFO_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", "", "", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MINELAYER_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponminelayer", _("^BG%s%s^K1 got too close to ^BG%s^K1's mine%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MINELAYER_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weaponminelayer", _("^BG%s^K1 forgot about their mine%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_WEAPON_MINSTANEX_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponminstanex", _("^BG%s%s^K1 has been vaporized by ^BG%s^K1's Minstanex%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MORTAR_MURDER_BOUNCE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapongrenadelauncher", _("^BG%s%s^K1 got too close to ^BG%s^K1's Mortar grenade%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MORTAR_MURDER_EXPLODE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapongrenadelauncher", _("^BG%s%s^K1 ate ^BG%s^K1's Mortar grenade%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MORTAR_SUICIDE_BOUNCE, 2, 1, "s1 s2loc spree_lost", "s1", "weapongrenadelauncher", _("^BG%s^K1 didn't see their own Mortar grenade%s%s"), "") \ @@@ -637,24 -683,18 +688,24 @@@ MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_ROUNDSTART, 0, 1, "", CPID_KEYHUNT_OTHER, "1 f1", _("^F4Round will start in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_SCAN, 0, 1, "", CPID_KEYHUNT_OTHER, "f1 0", _("^BGScanning frequency range..."), "") \ MULTITEAM_CENTER(1, CENTER_KEYHUNT_START_, 4, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGYou are starting with the ^TC^TT Key"), "") \ - MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_WAIT, 0, 4, "missing_teams", CPID_KEYHUNT_OTHER, "0 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") \ - MSG_CENTER_NOTIF(1, CENTER_MISSING_TEAMS, 0, 4, "missing_teams", CPID_MISSING_TEAMS, "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_WAIT, 0, 1, "missing_teams", CPID_KEYHUNT_OTHER, "0 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") \ ++ MSG_CENTER_NOTIF(1, CENTER_LMS_NOLIVES, 0, 0, "", CPID_LMS, "0 0", _("^BGYou have no lives left, you must wait until the next match"), "") \ + MSG_CENTER_NOTIF(1, CENTER_MISSING_TEAMS, 0, 1, "missing_teams", CPID_MISSING_TEAMS, "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") \ MSG_CENTER_NOTIF(1, CENTER_MISSING_PLAYERS, 0, 1, "f1", CPID_MISSING_PLAYERS, "-1 0", _("^BGWaiting for %s player(s) to join..."), "") \ - MSG_CENTER_NOTIF(1, CENTER_MINSTA_FINDAMMO, 0, 0, "", CPID_MINSTA_FINDAMMO, "1 9", _("^F4^COUNT^BG left to find some ammo!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_MINSTA_FINDAMMO_FIRST, 0, 0, "", CPID_MINSTA_FINDAMMO, "1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!")) \ - MSG_CENTER_NOTIF(1, CENTER_MINSTA_LIVES_REMAINING, 0, 1, "f1", NO_CPID, "0 0", _("^F2Extra lives remaining: ^K1%s"), "") \ - MSG_CENTER_NOTIF(1, CENTER_MINSTA_SECONDARY, 0, 0, "", NO_CPID, "0 0", _("^BGSecondary fire inflicts no damage!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_MOTD, 1, 0, "s1", CPID_MOTD, "-1 0", _("^BG%s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_FINDAMMO, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"1 9", _("^F4^COUNT^BG left to find some ammo!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_FINDAMMO_FIRST, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!")) \ + MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_LIVES_REMAINING, 0, 1, "f1", NO_CPID, "0 0", _("^F2Extra lives remaining: ^K1%s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_MOTD, 1, 0, "s1", CPID_MOTD, "-1 0", "^BG%s", "") \ MSG_CENTER_NOTIF(1, CENTER_NIX_COUNTDOWN, 0, 2, "item_wepname", CPID_NIX, "1 f2", _("^F2^COUNT^BG until weapon change...\nNext weapon: ^F1%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_NIX_NEWWEAPON, 0, 1, "item_wepname", CPID_NIX, "0 0", _("^F2Active weapon: ^F1%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_NADE, 0, 0, "", NO_CPID, "0 0", _("^BGPress ^F2DROPWEAPON^BG again to toss the grenade!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_NOTSHIELDED, 0, 0, "", CPID_ONSLAUGHT, "0 0", _("^K1Your generator is NOT shielded!\n^BGRe-capture controlpoints to shield it!"), "") \ MSG_CENTER_NOTIF(1, CENTER_OVERTIME_FRAG, 0, 0, "", CPID_OVERTIME, "0 0", _("^F2Now playing ^F4OVERTIME^F2!\nKeep fragging until we have a winner!"), _("^F2Now playing ^F4OVERTIME^F2!\nKeep scoring until we have a winner!")) \ - MSG_CENTER_NOTIF(1, CENTER_OVERTIME_CONTROLPOINT, 0, 0, "", CPID_OVERTIME, "3 0", _("^F2Now playing ^F4OVERTIME^F2!\n\nGenerators start now to decay.\nThe more control points your team holds,\nthe faster the enemy generator decays"), "") \ ++ MSG_CENTER_NOTIF(1, CENTER_OVERTIME_CONTROLPOINT, 0, 0, "", CPID_OVERTIME, "5 0", _("^F2Now playing ^F4OVERTIME^F2!\n\nGenerators are now decaying.\nThe more control points your team holds,\nthe faster the enemy generator decays"), "") \ MSG_CENTER_NOTIF(1, CENTER_OVERTIME_TIME, 0, 1, "f1time", CPID_OVERTIME, "0 0", _("^F2Now playing ^F4OVERTIME^F2!\n^BGAdded ^F4%s^BG to the game!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_PORTO_FAILED, 0, 0, "", NO_CPID, "0 0", _("^K1Portal deployment failed.\n\n^F2Catch it to try again!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_PORTO_CREATED_IN, 0, 0, "", NO_CPID, "0 0", _("^K1In^BG-portal created"), "") \ + MSG_CENTER_NOTIF(1, CENTER_PORTO_CREATED_OUT, 0, 0, "", NO_CPID, "0 0", _("^F3Out^BG-portal created"), "") \ ++ MSG_CENTER_NOTIF(1, CENTER_PORTO_FAILED, 0, 0, "", NO_CPID, "0 0", _("^K1Portal deployment failed.\n\n^F2Catch it to try again!"), "") \ MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_INVISIBILITY, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Invisibility has worn off"), "") \ MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_SHIELD, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Shield has worn off"), "") \ MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_SPEED, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Speed has worn off"), "") \ @@@ -675,7 -716,7 +727,8 @@@ MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_SPECTATE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Spectating in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_SUICIDE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Suicide in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_BEGINNING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout begins in ^COUNT"), "") \ - MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout ends in ^COUNT"), "") - MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout ends in ^COUNT"), "") ++ MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout ends in ^COUNT"), "") \ ++ MSG_CENTER_NOTIF(1, CENTER_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", NO_CPID, "0 0", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") #define MULTITEAM_MULTI2(default,prefix,anncepre,infopre,centerpre) \ MSG_MULTI_NOTIF(default, prefix##RED, anncepre##RED, infopre##RED, centerpre##RED) \ @@@ -764,13 -825,18 +837,19 @@@ MSG_MULTI_NOTIF(1, ITEM_WEAPON_NOAMMO, NO_MSG, INFO_ITEM_WEAPON_NOAMMO, CENTER_ITEM_WEAPON_NOAMMO) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_PRIMORSEC, NO_MSG, INFO_ITEM_WEAPON_PRIMORSEC, CENTER_ITEM_WEAPON_PRIMORSEC) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_UNAVAILABLE, NO_MSG, INFO_ITEM_WEAPON_UNAVAILABLE, CENTER_ITEM_WEAPON_UNAVAILABLE) \ + MSG_MULTI_NOTIF(1, MULTI_COINTOSS, NO_MSG, INFO_COINTOSS, CENTER_COINTOSS) \ MSG_MULTI_NOTIF(1, MULTI_COUNTDOWN_BEGIN, ANNCE_BEGIN, NO_MSG, CENTER_COUNTDOWN_BEGIN) \ - MSG_MULTI_NOTIF(1, MULTI_MINSTA_FINDAMMO, ANNCE_NUM_10, NO_MSG, CENTER_MINSTA_FINDAMMO_FIRST) \ + MSG_MULTI_NOTIF(1, MULTI_INSTAGIB_FINDAMMO, ANNCE_NUM_10, NO_MSG, CENTER_INSTAGIB_FINDAMMO_FIRST) \ MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_MURDER, NO_MSG, INFO_WEAPON_ACCORDEON_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_SUICIDE, NO_MSG, INFO_WEAPON_ACCORDEON_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ + MSG_MULTI_NOTIF(1, WEAPON_ARC_MURDER, NO_MSG, INFO_WEAPON_ARC_MURDER, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_BLASTER_MURDER, NO_MSG, INFO_WEAPON_BLASTER_MURDER, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_BLASTER_SUICIDE, NO_MSG, INFO_WEAPON_BLASTER_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ MSG_MULTI_NOTIF(1, WEAPON_CRYLINK_MURDER, NO_MSG, INFO_WEAPON_CRYLINK_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_CRYLINK_SUICIDE, NO_MSG, INFO_WEAPON_CRYLINK_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ + MSG_MULTI_NOTIF(1, WEAPON_DEVASTATOR_MURDER_DIRECT, NO_MSG, INFO_WEAPON_DEVASTATOR_MURDER_DIRECT, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_DEVASTATOR_MURDER_SPLASH, NO_MSG, INFO_WEAPON_DEVASTATOR_MURDER_SPLASH, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_DEVASTATOR_SUICIDE, NO_MSG, INFO_WEAPON_DEVASTATOR_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ MSG_MULTI_NOTIF(1, WEAPON_ELECTRO_MURDER_BOLT, NO_MSG, INFO_WEAPON_ELECTRO_MURDER_BOLT, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_ELECTRO_MURDER_COMBO, NO_MSG, INFO_WEAPON_ELECTRO_MURDER_COMBO, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_ELECTRO_MURDER_ORBS, NO_MSG, INFO_WEAPON_ELECTRO_MURDER_ORBS, NO_MSG) \ @@@ -788,11 -856,10 +869,11 @@@ MSG_MULTI_NOTIF(1, WEAPON_HOOK_MURDER, NO_MSG, INFO_WEAPON_HOOK_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_KLEINBOTTLE_MURDER, NO_MSG, INFO_WEAPON_KLEINBOTTLE_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_KLEINBOTTLE_SUICIDE, NO_MSG, INFO_WEAPON_KLEINBOTTLE_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ - MSG_MULTI_NOTIF(1, WEAPON_LASER_MURDER, NO_MSG, INFO_WEAPON_LASER_MURDER, NO_MSG) \ - MSG_MULTI_NOTIF(1, WEAPON_LASER_SUICIDE, NO_MSG, INFO_WEAPON_LASER_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ + MSG_MULTI_NOTIF(1, WEAPON_MACHINEGUN_MURDER_SNIPE, NO_MSG, INFO_WEAPON_MACHINEGUN_MURDER_SNIPE, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_MACHINEGUN_MURDER_SPRAY, NO_MSG, INFO_WEAPON_MACHINEGUN_MURDER_SPRAY, NO_MSG) \ ++ MSG_MULTI_NOTIF(1, WEAPON_MINELAYER_LIMIT, NO_MSG, INFO_WEAPON_MINELAYER_LIMIT, CENTER_WEAPON_MINELAYER_LIMIT) \ MSG_MULTI_NOTIF(1, WEAPON_MINELAYER_MURDER, NO_MSG, INFO_WEAPON_MINELAYER_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MINELAYER_SUICIDE, NO_MSG, INFO_WEAPON_MINELAYER_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ - MSG_MULTI_NOTIF(1, WEAPON_MINSTANEX_MURDER, NO_MSG, INFO_WEAPON_MINSTANEX_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MORTAR_MURDER_BOUNCE, NO_MSG, INFO_WEAPON_MORTAR_MURDER_BOUNCE, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MORTAR_MURDER_EXPLODE, NO_MSG, INFO_WEAPON_MORTAR_MURDER_EXPLODE, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MORTAR_SUICIDE_BOUNCE, NO_MSG, INFO_WEAPON_MORTAR_SUICIDE_BOUNCE, CENTER_DEATH_SELF_GENERIC) \ diff --cc qcsrc/common/weapons/w_minelayer.qc index 000000000,970192e31..2dac41e1b mode 000000,100644..100644 --- a/qcsrc/common/weapons/w_minelayer.qc +++ b/qcsrc/common/weapons/w_minelayer.qc @@@ -1,0 -1,622 +1,622 @@@ + #ifdef REGISTER_WEAPON + REGISTER_WEAPON( + /* WEP_##id */ MINE_LAYER, + /* function */ W_MineLayer, + /* ammotype */ ammo_rockets, + /* impulse */ 4, + /* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, + /* rating */ BOT_PICKUP_RATING_HIGH, + /* color */ '0.75 1 0', + /* modelname */ "minelayer", + /* simplemdl */ "foobar", + /* crosshair */ "gfx/crosshairminelayer 0.9", + /* wepimg */ "weaponminelayer", + /* refname */ "minelayer", + /* wepname */ _("Mine Layer") + ); + + #define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer) + #define MINELAYER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, ammo) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, damageforcescale) \ + w_cvar(id, sn, NONE, detonatedelay) \ + w_cvar(id, sn, NONE, edgedamage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, health) \ + w_cvar(id, sn, NONE, lifetime) \ + w_cvar(id, sn, NONE, lifetime_countdown) \ + w_cvar(id, sn, NONE, limit) \ + w_cvar(id, sn, NONE, protection) \ + w_cvar(id, sn, NONE, proximityradius) \ + w_cvar(id, sn, NONE, radius) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, remote_damage) \ + w_cvar(id, sn, NONE, remote_edgedamage) \ + w_cvar(id, sn, NONE, remote_force) \ + w_cvar(id, sn, NONE, remote_radius) \ + w_cvar(id, sn, NONE, speed) \ + w_cvar(id, sn, NONE, time) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + + #ifdef SVQC + MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) + void W_MineLayer_Think(void); + .float minelayer_detonate, mine_explodeanyway; + .float mine_time; + .vector mine_orientation; + #endif + #else + #ifdef SVQC + void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER); } + + void W_MineLayer_Stick(entity to) + { + spamsound(self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM); + + // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile + + entity newmine; + newmine = spawn(); + newmine.classname = self.classname; + + newmine.bot_dodge = self.bot_dodge; + newmine.bot_dodgerating = self.bot_dodgerating; + + newmine.owner = self.owner; + newmine.realowner = self.realowner; + setsize(newmine, '-4 -4 -4', '4 4 4'); + setorigin(newmine, self.origin); + setmodel(newmine, "models/mine.md3"); + newmine.angles = vectoangles(-trace_plane_normal); // face against the surface + + newmine.mine_orientation = -trace_plane_normal; + + newmine.takedamage = self.takedamage; + newmine.damageforcescale = self.damageforcescale; + newmine.health = self.health; + newmine.event_damage = self.event_damage; + newmine.spawnshieldtime = self.spawnshieldtime; + newmine.damagedbycontents = TRUE; + + newmine.movetype = MOVETYPE_NONE; // lock the mine in place + newmine.projectiledeathtype = self.projectiledeathtype; + + newmine.mine_time = self.mine_time; + + newmine.touch = func_null; + newmine.think = W_MineLayer_Think; + newmine.nextthink = time; + newmine.cnt = self.cnt; + newmine.flags = self.flags; + + remove(self); + self = newmine; + + if(to) + SetMovetypeFollow(self, to); + } + + void W_MineLayer_Explode(void) + { + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other); + + if(self.realowner.weapon == WEP_MINE_LAYER) + { + entity oldself; + oldself = self; + self = self.realowner; + if(!WEP_ACTION(WEP_MINE_LAYER, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove(self); + } + + void W_MineLayer_DoRemoteExplode(void) + { + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) + self.velocity = self.mine_orientation; // particle fx and decals need .velocity + + RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if(self.realowner.weapon == WEP_MINE_LAYER) + { + entity oldself; + oldself = self; + self = self.realowner; + if(!WEP_ACTION(WEP_MINE_LAYER, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove(self); + } + + void W_MineLayer_RemoteExplode(void) + { + if(self.realowner.deadflag == DEAD_NO) + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device + ) + { + W_MineLayer_DoRemoteExplode(); + } + } + + void W_MineLayer_ProximityExplode(void) + { + // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance + if(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0) + { + entity head; + head = findradius(self.origin, WEP_CVAR(minelayer, radius)); + while(head) + { + if(head == self.realowner || SAME_TEAM(head, self.realowner)) + return; + head = head.chain; + } + } + + self.mine_time = 0; + W_MineLayer_Explode(); + } + + float W_MineLayer_Count(entity e) + { + float minecount = 0; + entity mine; + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e) + minecount += 1; + + return minecount; + } + + void W_MineLayer_Think(void) + { + entity head; + + self.nextthink = time; + + if(self.movetype == MOVETYPE_FOLLOW) + { + if(LostMovetypeFollow(self)) + { + UnsetMovetypeFollow(self); + self.movetype = MOVETYPE_NONE; + } + } + + // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this + // TODO: replace this mine_trigger.wav sound with a real countdown + if((time > self.cnt) && (!self.mine_time) && (self.cnt > 0)) + { + if(WEP_CVAR(minelayer, lifetime_countdown) > 0) + spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + self.mine_time = time + WEP_CVAR(minelayer, lifetime_countdown); + self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near. + } + + // a player's mines shall explode if he disconnects or dies + // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams? + if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.frozen) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_MineLayer_Explode(); + return; + } + + // set the mine for detonation when a foe gets close enough + head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius)); + while(head) + { + if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen) + if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates + if(!self.mine_time) + { + spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + self.mine_time = time + WEP_CVAR(minelayer, time); + } + head = head.chain; + } + + // explode if it's time to + if(self.mine_time && time >= self.mine_time) + { + W_MineLayer_ProximityExplode(); + return; + } + + // remote detonation + if(self.realowner.weapon == WEP_MINE_LAYER) + if(self.realowner.deadflag == DEAD_NO) + if(self.minelayer_detonate) + W_MineLayer_RemoteExplode(); + } + + void W_MineLayer_Touch(void) + { + if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) + return; // we're already a stuck mine, why do we get called? TODO does this even happen? + + if(WarpZone_Projectile_Touch()) + { + if(wasfreed(self)) + self.realowner.minelayer_mines -= 1; + return; + } + + if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO) + { + // hit a player + // don't stick + } + else + { + W_MineLayer_Stick(other); + } + } + + void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) + { + if(self.health <= 0) + return; + + float is_from_enemy = (inflictor.realowner != self.realowner); + + if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1))) + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + self.angles = vectoangles(self.velocity); + + if(self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_MineLayer_Explode); + } + + void W_MineLayer_Attack(void) + { + entity mine; + entity flash; + + // scan how many mines we placed, and return if we reached our limit + if(WEP_CVAR(minelayer, limit)) + { + if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) + { + // the refire delay keeps this message from being spammed - sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(WEP_CVAR(minelayer, limit)), " ^7mines at a time\n") ); ++ Send_Notification(NOTIF_ONE, self, MSG_MULTI, WEAPON_MINELAYER_LIMIT, WEP_CVAR(minelayer, limit)); + play2(self, "weapons/unavailable.wav"); + return; + } + } + + W_DecreaseAmmo(WEP_CVAR(minelayer, ammo)); + + W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, WEP_CVAR(minelayer, damage)); + pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + mine = WarpZone_RefSys_SpawnSameRefSys(self); + mine.owner = mine.realowner = self; + if(WEP_CVAR(minelayer, detonatedelay) >= 0) + mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay); + else + mine.spawnshieldtime = -1; + mine.classname = "mine"; + mine.bot_dodge = TRUE; + mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous + + mine.takedamage = DAMAGE_YES; + mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale); + mine.health = WEP_CVAR(minelayer, health); + mine.event_damage = W_MineLayer_Damage; + mine.damagedbycontents = TRUE; + + mine.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(mine); + mine.projectiledeathtype = WEP_MINE_LAYER; + setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot + + setorigin(mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point + W_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0); + mine.angles = vectoangles(mine.velocity); + + mine.touch = W_MineLayer_Touch; + mine.think = W_MineLayer_Think; + mine.nextthink = time; + mine.cnt = (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown)); + mine.flags = FL_PROJECTILE; + mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY; + + if(mine.cnt > 0) { mine.cnt += time; } + + CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE); + + // muzzle flash for 1st person view + flash = spawn(); + setmodel(flash, "models/flash.md3"); // precision set below + SUB_SetFade(flash, time, 0.1); + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); + + // common properties + + other = mine; MUTATOR_CALLHOOK(EditProjectile); + + self.minelayer_mines = W_MineLayer_Count(self); + } + + float W_MineLayer_PlacedMines(float detonate) + { + entity mine; + float minfound = 0; + + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self) + { + if(detonate) + { + if(!mine.minelayer_detonate) + { + mine.minelayer_detonate = TRUE; + minfound = 1; + } + } + else + minfound = 1; + } + return minfound; + } + + float W_MineLayer(float req) + { + entity mine; + float ammo_amount; + switch(req) + { + case WR_AIM: + { + // aim and decide to fire if appropriate + if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) + self.BUTTON_ATCK = FALSE; + else + self.BUTTON_ATCK = bot_aim(WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), FALSE); + if(skill >= 2) // skill 0 and 1 bots won't detonate mines! + { + // decide whether to detonate mines + entity targetlist, targ; + float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; + float selfdamage, teamdamage, enemydamage; + edgedamage = WEP_CVAR(minelayer, edgedamage); + coredamage = WEP_CVAR(minelayer, damage); + edgeradius = WEP_CVAR(minelayer, radius); + recipricoledgeradius = 1 / edgeradius; + selfdamage = 0; + teamdamage = 0; + enemydamage = 0; + targetlist = findchainfloat(bot_attack, TRUE); + mine = find(world, classname, "mine"); + while(mine) + { + if(mine.realowner != self) + { + mine = find(mine, classname, "mine"); + continue; + } + targ = targetlist; + while(targ) + { + d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin); + d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); + // count potential damage according to type of target + if(targ == self) + selfdamage = selfdamage + d; + else if(targ.team == self.team && teamplay) + teamdamage = teamdamage + d; + else if(bot_shouldattack(targ)) + enemydamage = enemydamage + d; + targ = targ.chain; + } + mine = find(mine, classname, "mine"); + } + float desirabledamage; + desirabledamage = enemydamage; + if(time > self.invincible_finished && time > self.spawnshieldtime) + desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; + if(teamplay && self.team) + desirabledamage = desirabledamage - teamdamage; + + mine = find(world, classname, "mine"); + while(mine) + { + if(mine.realowner != self) + { + mine = find(mine, classname, "mine"); + continue; + } + makevectors(mine.v_angle); + targ = targetlist; + if(skill > 9) // normal players only do this for the target they are tracking + { + targ = targetlist; + while(targ) + { + if( + (v_forward * normalize(mine.origin - targ.origin)< 0.1) + && desirabledamage > 0.1*coredamage + )self.BUTTON_ATCK2 = TRUE; + targ = targ.chain; + } + }else{ + float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); + //As the distance gets larger, a correct detonation gets near imposible + //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player + if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1) + if(IS_PLAYER(self.enemy)) + if(desirabledamage >= 0.1*coredamage) + if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + self.BUTTON_ATCK2 = TRUE; + // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); + } + + mine = find(mine, classname, "mine"); + } + // if we would be doing at X percent of the core damage, detonate it + // but don't fire a new shot at the same time! + if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events + self.BUTTON_ATCK2 = TRUE; + if((skill > 6.5) && (selfdamage > self.health)) + self.BUTTON_ATCK2 = FALSE; + //if(self.BUTTON_ATCK2 == TRUE) + // dprint(ftos(desirabledamage),"\n"); + if(self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; + } + + return TRUE; + } + case WR_THINK: + { + if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload + { + // not if we're holding the minelayer without enough ammo, but can detonate existing mines + if(!(W_MineLayer_PlacedMines(FALSE) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo))) + WEP_ACTION(self.weapon, WR_RELOAD); + } + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire))) + { + W_MineLayer_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready); + } + } + + if(self.BUTTON_ATCK2) + { + if(W_MineLayer_PlacedMines(TRUE)) + sound(self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM); + } + + return TRUE; + } + case WR_INIT: + { + precache_model("models/flash.md3"); + precache_model("models/mine.md3"); + precache_model("models/weapons/g_minelayer.md3"); + precache_model("models/weapons/v_minelayer.md3"); + precache_model("models/weapons/h_minelayer.iqm"); + precache_sound("weapons/mine_det.wav"); + precache_sound("weapons/mine_fire.wav"); + precache_sound("weapons/mine_stick.wav"); + precache_sound("weapons/mine_trigger.wav"); + MINELAYER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_CHECKAMMO1: + { + // don't switch while placing a mine + if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER) + { + ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo); + ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= WEP_CVAR(minelayer, ammo); + return ammo_amount; + } + return TRUE; + } + case WR_CHECKAMMO2: + { + if(W_MineLayer_PlacedMines(FALSE)) + return TRUE; + else + return FALSE; + } + case WR_CONFIG: + { + MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_RESETPLAYER: + { + self.minelayer_mines = 0; + return TRUE; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(minelayer, ammo), "weapons/reload.wav"); + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_MINELAYER_SUICIDE; + } + case WR_KILLMESSAGE: + { + return WEAPON_MINELAYER_MURDER; + } + } + return FALSE; + } + #endif + #ifdef CSQC + float W_MineLayer(float req) + { + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM); + + return TRUE; + } + case WR_INIT: + { + precache_sound("weapons/mine_exp.wav"); + return TRUE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; + } + #endif + #endif diff --cc qcsrc/common/weapons/w_porto.qc index 000000000,49fb47e43..e1fb82f8f mode 000000,100644..100644 --- a/qcsrc/common/weapons/w_porto.qc +++ b/qcsrc/common/weapons/w_porto.qc @@@ -1,0 -1,422 +1,422 @@@ + #ifdef REGISTER_WEAPON + REGISTER_WEAPON( + /* WEP_##id */ PORTO, + /* function */ W_Porto, + /* ammotype */ ammo_none, + /* impulse */ 0, + /* flags */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, + /* rating */ 0, + /* color */ '0.5 0.5 0.5', + /* modelname */ "porto", + /* simplemdl */ "foobar", + /* crosshair */ "gfx/crosshairporto 0.6", + /* wepimg */ "weaponporto", + /* refname */ "porto", + /* wepname */ _("Port-O-Launch") + ); + + #define PORTO_SETTINGS(w_cvar,w_prop) PORTO_SETTINGS_LIST(w_cvar, w_prop, PORTO, porto) + #define PORTO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, NONE, secondary) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + + #ifdef SVQC + PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) + .entity porto_current; + .vector porto_v_angle; // holds "held" view angles + .float porto_v_angle_held; + .vector right_vector; + #endif + #else + #ifdef SVQC + void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO); } + + void W_Porto_Success(void) + { + if(self.realowner == world) + { + objerror("Cannot succeed successfully: no owner\n"); + return; + } + + self.realowner.porto_current = world; + remove(self); + } + + string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo); + void W_Porto_Fail(float failhard) + { + if(self.realowner == world) + { + objerror("Cannot fail successfully: no owner\n"); + return; + } + + // no portals here! + if(self.cnt < 0) + { + Portal_ClearWithID(self.realowner, self.portal_id); + } + + self.realowner.porto_current = world; + + if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !(self.realowner.weapons & WEPSET_PORTO)) + { + setsize(self, '-16 -16 0', '16 16 32'); + setorigin(self, self.origin + trace_plane_normal); + if(move_out_of_solid(self)) + { + self.flags = FL_ITEM; + self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128); + tracetoss(self, self); + if(vlen(trace_endpos - self.realowner.origin) < 128) + { + W_ThrowNewWeapon(self.realowner, WEP_PORTO, 0, self.origin, self.velocity); - centerprint(self.realowner, "^1Portal deployment failed.\n\n^2Catch it to try again!"); ++ Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_FAILED); + } + } + } + remove(self); + } + + void W_Porto_Remove(entity p) + { + if(p.porto_current.realowner == p && p.porto_current.classname == "porto") + { + entity oldself; + oldself = self; + self = p.porto_current; + W_Porto_Fail(1); + self = oldself; + } + } + + void W_Porto_Think(void) + { + trace_plane_normal = '0 0 0'; + if(self.realowner.playerid != self.playerid) + remove(self); + else + W_Porto_Fail(0); + } + + void W_Porto_Touch(void) + { + vector norm; + + // do not use PROJECTILE_TOUCH here + // FIXME but DO handle warpzones! + + if(other.classname == "portal") + return; // handled by the portal + + norm = trace_plane_normal; + if(trace_ent.iscreature) + { + traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_z, MOVE_WORLDONLY, self); + if(trace_fraction >= 1) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + return; + } + + if(self.realowner.playerid != self.playerid) + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + remove(self); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + { + spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTEN_NORM); + // just reflect + self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal); + self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal)); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + W_Porto_Fail(0); + if(self.cnt < 0) + Portal_ClearAll_PortalsOnly(self.realowner); + } + else if(self.cnt == 0) + { + // in-portal only + if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; - centerprint(self.realowner, "^1In^7-portal created."); ++ Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + W_Porto_Fail(0); + } + } + else if(self.cnt == 1) + { + // out-portal only + if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; - centerprint(self.realowner, "^4Out^7-portal created."); ++ Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + W_Porto_Fail(0); + } + } + else if(self.effects & EF_RED) + { + self.effects += EF_BLUE - EF_RED; + if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; - centerprint(self.realowner, "^1In^7-portal created."); ++ Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN); + self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm); + self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm)); + CSQCProjectile(self, TRUE, PROJECTILE_PORTO_BLUE, TRUE); // change type + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { + if(self.realowner.portal_in.portal_id == self.portal_id) + { + if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; - centerprint(self.realowner, "^4Out^7-portal created."); ++ Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + } + + void W_Porto_Attack(float type) + { + entity gren; + + W_SetupShot(self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0); + // always shoot from the eye + w_shotdir = v_forward; + w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward; + + //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn(); + gren.cnt = type; + gren.owner = gren.realowner = self; + gren.playerid = self.playerid; + gren.classname = "porto"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = 200; + gren.movetype = MOVETYPE_BOUNCEMISSILE; + PROJECTILE_MAKETRIGGER(gren); + gren.effects = EF_RED; + gren.scale = 4; + setorigin(gren, w_shotorg); + setsize(gren, '0 0 0', '0 0 0'); + + gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime); + gren.think = W_Porto_Think; + gren.touch = W_Porto_Touch; + + if(self.items & IT_STRENGTH) + W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0); + else + W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed), 0); + + gren.angles = vectoangles(gren.velocity); + gren.flags = FL_PROJECTILE; + + gren.portal_id = time; + self.porto_current = gren; + gren.playerid = self.playerid; + fixedmakevectors(fixedvectoangles(gren.velocity)); + gren.right_vector = v_right; + + gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; + + if(type > 0) + CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); + } + + float w_nexball_weapon(float req); // WEAPONTODO + float W_Porto(float req) + { + //vector v_angle_save; + + if(g_nexball) { return w_nexball_weapon(req); } + + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = FALSE; + self.BUTTON_ATCK2 = FALSE; + if(!WEP_CVAR(porto, secondary)) + if(bot_aim(WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), FALSE)) + self.BUTTON_ATCK = TRUE; + + return TRUE; + } + case WR_CONFIG: + { + PORTO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_THINK: + { + if(WEP_CVAR(porto, secondary)) + { + if(self.BUTTON_ATCK) + if(!self.porto_current) + if(!self.porto_forbidden) + if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire))) + { + W_Porto_Attack(0); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); + } + + if(self.BUTTON_ATCK2) + if(!self.porto_current) + if(!self.porto_forbidden) + if(weapon_prepareattack(1, WEP_CVAR_SEC(porto, refire))) + { + W_Porto_Attack(1); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready); + } + } + else + { + if(self.porto_v_angle_held) + { + if(!self.BUTTON_ATCK2) + { + self.porto_v_angle_held = 0; + + ClientData_Touch(self); + } + } + else + { + if(self.BUTTON_ATCK2) + { + self.porto_v_angle = self.v_angle; + self.porto_v_angle_held = 1; + + ClientData_Touch(self); + } + } + if(self.porto_v_angle_held) + makevectors(self.porto_v_angle); // override the previously set angles + + if(self.BUTTON_ATCK) + if(!self.porto_current) + if(!self.porto_forbidden) + if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire))) + { + W_Porto_Attack(-1); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); + } + } + + return TRUE; + } + case WR_INIT: + { + precache_model("models/weapons/g_porto.md3"); + precache_model("models/weapons/v_porto.md3"); + precache_model("models/weapons/h_porto.iqm"); + precache_model("models/portal.md3"); + precache_sound("porto/bounce.wav"); + precache_sound("porto/create.wav"); + precache_sound("porto/expire.wav"); + precache_sound("porto/explode.wav"); + precache_sound("porto/fire.wav"); + precache_sound("porto/unsupported.wav"); + PORTO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + return TRUE; + } + case WR_RESETPLAYER: + { + self.porto_current = world; + return TRUE; + } + } + return FALSE; + } + #endif + #ifdef CSQC + float W_Porto(float req) + { + switch(req) + { + case WR_IMPACTEFFECT: + { + print("Since when does Porto send DamageInfo?\n"); + return TRUE; + } + case WR_INIT: + { + // nothing to do + return TRUE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; + } + #endif + #endif diff --cc qcsrc/server/cl_client.qc index 1ab2b7ab5,b1dccc7b5..be379590a --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -1577,44 -1542,52 +1542,52 @@@ float CalcRotRegen(float current, floa void player_regen (void) { - float minh, mina, minf, maxh, maxa, maxf, limith, limita, limitf, max_mod, regen_mod, rot_mod, limit_mod; - maxh = autocvar_g_balance_health_rotstable; - maxa = autocvar_g_balance_armor_rotstable; - maxf = autocvar_g_balance_fuel_rotstable; - minh = autocvar_g_balance_health_regenstable; - mina = autocvar_g_balance_armor_regenstable; - minf = autocvar_g_balance_fuel_regenstable; - limith = autocvar_g_balance_health_limit; - limita = autocvar_g_balance_armor_limit; - limitf = autocvar_g_balance_fuel_limit; - + float max_mod, regen_mod, rot_mod, limit_mod; max_mod = regen_mod = rot_mod = limit_mod = 1; + regen_mod_max = max_mod; + regen_mod_regen = regen_mod; + regen_mod_rot = rot_mod; + regen_mod_limit = limit_mod; + if(!MUTATOR_CALLHOOK(PlayerRegen)) + if(!self.frozen) + { + float minh, mina, maxh, maxa, limith, limita; + maxh = autocvar_g_balance_health_rotstable; + maxa = autocvar_g_balance_armor_rotstable; + minh = autocvar_g_balance_health_regenstable; + mina = autocvar_g_balance_armor_regenstable; + limith = autocvar_g_balance_health_limit; + limita = autocvar_g_balance_armor_limit; - ++ + max_mod = regen_mod_max; + regen_mod = regen_mod_regen; + rot_mod = regen_mod_rot; + limit_mod = regen_mod_limit; + + maxh = maxh * max_mod; + minh = minh * max_mod; + limith = limith * limit_mod; + limita = limita * limit_mod; - maxh = maxh * max_mod; - //maxa = maxa * max_mod; - //maxf = maxf * max_mod; - minh = minh * max_mod; - //mina = mina * max_mod; - //minf = minf * max_mod; - limith = limith * limit_mod; - limita = limita * limit_mod; - //limitf = limitf * limit_mod; + self.armorvalue = CalcRotRegen(self.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > self.pauserotarmor_finished), limita); + self.health = CalcRotRegen(self.health, minh, autocvar_g_balance_health_regen, autocvar_g_balance_health_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxh, autocvar_g_balance_health_rot, autocvar_g_balance_health_rotlinear, rot_mod * frametime * (time > self.pauserothealth_finished), limith); + } - if(g_ca) - rot_mod = 0; + // if player rotted to death... die! + // check this outside above checks, as player may still be able to rot to death + if(self.health < 1) + self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0'); - if (!g_minstagib && !g_ca && (!g_lms || autocvar_g_lms_regenerate)) + if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) { - self.armorvalue = CalcRotRegen(self.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > self.pauserotarmor_finished), limita); - self.health = CalcRotRegen(self.health, minh, autocvar_g_balance_health_regen, autocvar_g_balance_health_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxh, autocvar_g_balance_health_rot, autocvar_g_balance_health_rotlinear, rot_mod * frametime * (time > self.pauserothealth_finished), limith); + float minf, maxf, limitf; - // if player rotted to death... die! - if(self.health < 1) - self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0'); - } + maxf = autocvar_g_balance_fuel_rotstable; + minf = autocvar_g_balance_fuel_regenstable; + limitf = autocvar_g_balance_fuel_limit; - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished) * ((self.items & IT_FUEL_REGEN) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rot_mod * frametime * (time > self.pauserotfuel_finished), limitf); + self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > self.pauseregen_finished) * ((self.items & IT_FUEL_REGEN) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > self.pauserotfuel_finished), limitf); + } } float zoomstate_set; @@@ -1774,7 -1752,7 +1752,6 @@@ float SpectateUpdate( return 1; } -- float SpectateSet() { if(self.enemy.classname != "player") @@@ -2594,8 -2623,8 +2622,6 @@@ void PlayerPostThink (void } */ -- //pointparticles(particleeffectnum("machinegun_impact"), self.origin + self.view_ofs + '0 0 7', '0 0 0', 1); -- if(self.waypointsprite_attachedforcarrier) WaypointSprite_UpdateHealth(self.waypointsprite_attachedforcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); diff --cc qcsrc/server/command/cmd.qc index 4850049e5,7d67c4f30..898e7db18 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@@ -150,9 -150,11 +150,11 @@@ void ClientCommand_join(float request { if(IS_CLIENT(self)) { - if(!IS_PLAYER(self) && !lockteams && !g_arena) - if(!IS_PLAYER(self) && !lockteams) ++ if(!IS_PLAYER(self) && !lockteams && !gameover) { - if(nJoinAllowed(self)) + if(self.caplayer) + return; + if(nJoinAllowed(self)) { if(autocvar_g_campaign) { campaign_bots_may_start = 1; } @@@ -302,7 -461,19 +461,19 @@@ void ClientCommand_selectteam(float req else if(self.wasplayer && autocvar_g_changeteam_banned) sprint(self, "^1You cannot change team, forbidden by the server.\n"); else + { + if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance) + { + CheckAllowedTeams(self); + GetTeamCounts(self); + if(!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(self.team), self)) + { - sprint(self, "Cannot change to a larger/better/shinier team\n"); ++ Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM); + return; + } + } ClientKill_TeamChange(selection); + } } } else diff --cc qcsrc/server/command/sv_cmd.qc index 1a6a5513c,edeb571c0..27ed6b869 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@@ -435,15 -478,17 +478,14 @@@ void GameCommand_cointoss(float request { case CMD_REQUEST_COMMAND: { - entity client; - string result1 = (argv(2) ? strcat("^7", argv(1), "^3!\n") : "^1HEADS^3!\n"); - string result2 = (argv(2) ? strcat("^7", argv(2), "^3!\n") : "^4TAILS^3!\n"); + string result1 = (argv(2) ? strcat("^7", argv(1)) : "^1HEADS"); + string result2 = (argv(2) ? strcat("^7", argv(2)) : "^4TAILS"); string choice = ((random() > 0.5) ? result1 : result2); - - FOR_EACH_CLIENT(client) - centerprint(client, strcat("^3Throwing coin... Result: ", choice)); - bprint(strcat("^3Throwing coin... Result: ", choice)); + + Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_COINTOSS, choice); - return; } - + default: case CMD_REQUEST_USAGE: { diff --cc qcsrc/server/item_key.qc index 08429ca6f,f181f3764..79003853a --- a/qcsrc/server/item_key.qc +++ b/qcsrc/server/item_key.qc @@@ -338,10 -338,10 +338,10 @@@ void trigger_keylock_touch(void) } else if (other.key_door_messagetime <= time) { // no keys were given play2(other, self.noise2); - centerprint(other, strcat("You need ", item_keys_keylist(self.itemkeys), "!")); + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(self.itemkeys)); other.key_door_messagetime = time + 2; } - + // trigger target2 if (self.delay <= time || started_delay == TRUE) if (self.target2) { diff --cc qcsrc/server/mutators/gamemode_assault.qc index fa12b8bdb,1eb2b77cb..8a8c50b38 --- a/qcsrc/server/mutators/gamemode_assault.qc +++ b/qcsrc/server/mutators/gamemode_assault.qc @@@ -539,10 -523,10 +516,10 @@@ void havocbot_ast_reset_role(entity bot MUTATOR_HOOKFUNCTION(assault_PlayerSpawn) { if(self.team == assault_attacker_team) - centerprint(self, "You are attacking!"); + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING); else - centerprint(self, "You are defending!"); + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING); - + return FALSE; }