From 6221261623a6ebf63ac3f5e2c3f80e44b03b9b07 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 11 Feb 2025 16:46:18 +0000 Subject: [PATCH] Make monster item drops customizable --- monsters.cfg | 6 ++ qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc | 8 +- qcsrc/common/monsters/monster/golem.qc | 4 +- qcsrc/common/monsters/monster/mage.qc | 3 +- qcsrc/common/monsters/monster/spider.qc | 3 +- qcsrc/common/monsters/monster/wyvern.qc | 3 +- qcsrc/common/monsters/monster/zombie.qc | 3 +- qcsrc/common/monsters/sv_monsters.qc | 78 +++++++++++++------ qcsrc/common/monsters/sv_monsters.qh | 3 + .../mutators/mutator/instagib/sv_instagib.qc | 4 +- .../mutators/mutator/overkill/sv_overkill.qc | 5 +- qcsrc/server/mutators/events.qh | 11 ++- 12 files changed, 94 insertions(+), 37 deletions(-) diff --git a/monsters.cfg b/monsters.cfg index 4b40ac019..5669e2696 100644 --- a/monsters.cfg +++ b/monsters.cfg @@ -7,6 +7,7 @@ set g_monster_zombie_attack_melee_damage 55 set g_monster_zombie_attack_melee_delay 1 set g_monster_zombie_damageforcescale 0.55 set g_monster_zombie_health 200 +set g_monster_zombie_loot "health_medium" set g_monster_zombie_speed_run 600 set g_monster_zombie_speed_stop 100 set g_monster_zombie_speed_walk 300 @@ -21,6 +22,7 @@ set g_monster_spider_attack_web_speed 1300 set g_monster_spider_attack_web_speed_up 150 set g_monster_spider_damageforcescale 0.6 set g_monster_spider_health 180 +set g_monster_spider_loot "health_medium" set g_monster_spider_speed_run 500 set g_monster_spider_speed_stop 100 set g_monster_spider_speed_walk 400 @@ -53,6 +55,7 @@ set g_monster_mage_heal_delay 1.5 set g_monster_mage_heal_minhealth 250 set g_monster_mage_heal_range 250 set g_monster_mage_health 400 +set g_monster_mage_loot "health_big" set g_monster_mage_shield_blockpercent 0.8 set g_monster_mage_shield_delay 7 set g_monster_mage_shield_time 3 @@ -69,6 +72,7 @@ set g_monster_wyvern_attack_fireball_radius 120 set g_monster_wyvern_attack_fireball_speed 1200 set g_monster_wyvern_damageforcescale 0.6 set g_monster_wyvern_health 150 +set g_monster_wyvern_loot "cells" set g_monster_wyvern_speed_run 250 set g_monster_wyvern_speed_stop 300 set g_monster_wyvern_speed_walk 120 @@ -87,6 +91,7 @@ set g_monster_golem_attack_smash_force 100 set g_monster_golem_attack_smash_range 200 set g_monster_golem_damageforcescale 0.1 set g_monster_golem_health 650 +set g_monster_golem_loot "health_mega electro" set g_monster_golem_speed_run 320 set g_monster_golem_speed_stop 300 set g_monster_golem_speed_walk 150 @@ -98,6 +103,7 @@ set g_monsters_edit 0 set g_monsters_skill 1 "monster skill (affecting some of their attributes); \"1\" = easy, \"2\" = medium, \"3\" = hard, \"4\" = insane, \"5\" = nightmare" set g_monsters_miniboss_chance 5 set g_monsters_miniboss_healthboost 100 +set g_monsters_miniboss_loot "vortex" "space-separated list of items added as a potential drop when a miniboss dies" set g_monsters_drop 1 set g_monsters_drop_time 10 set g_monsters_ignoretraces 1 diff --git a/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc b/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc index f90030895..67028f0b2 100644 --- a/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc +++ b/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc @@ -285,12 +285,18 @@ MUTATOR_HOOKFUNCTION(cts, FilterItem) if (ITEM_IS_LOOT(item)) { - if(item.monster_loot && autocvar_g_cts_drop_monster_items) + if(item.monster_item && autocvar_g_cts_drop_monster_items) return false; return true; } } +MUTATOR_HOOKFUNCTION(cts, MonsterDropItem) +{ + if(!autocvar_g_cts_drop_monster_items) + M_ARGV(1, string) = ""; +} + MUTATOR_HOOKFUNCTION(cts, Damage_Calculate) { entity frag_attacker = M_ARGV(1, entity); diff --git a/qcsrc/common/monsters/monster/golem.qc b/qcsrc/common/monsters/monster/golem.qc index e98aa3e45..c2a6367c1 100644 --- a/qcsrc/common/monsters/monster/golem.qc +++ b/qcsrc/common/monsters/monster/golem.qc @@ -2,6 +2,7 @@ #ifdef SVQC float autocvar_g_monster_golem_health; +string autocvar_g_monster_golem_loot = "health_mega electro"; float autocvar_g_monster_golem_damageforcescale = 0.1; float autocvar_g_monster_golem_attack_smash_damage; float autocvar_g_monster_golem_attack_smash_force = 100; @@ -269,8 +270,7 @@ METHOD(Golem, mr_setup, bool(Golem this, entity actor)) if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_golem_speed_stop); } if(!actor.damageforcescale) { actor.damageforcescale = (autocvar_g_monster_golem_damageforcescale); } - actor.monster_loot = ITEM_HealthMega; - actor.weapon = WEP_ELECTRO.m_id; // matches attacks better than WEP_VORTEX + actor.monster_loot = autocvar_g_monster_golem_loot; setanim(actor, actor.anim_spawn, false, true, true); actor.spawn_time = actor.animstate_endtime; diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc index 13a9c513d..4600fe2e7 100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@ -2,6 +2,7 @@ #ifdef SVQC float autocvar_g_monster_mage_health; +string autocvar_g_monster_mage_loot = "health_big"; float autocvar_g_monster_mage_damageforcescale = 0.5; float autocvar_g_monster_mage_attack_spike_damage; float autocvar_g_monster_mage_attack_spike_radius; @@ -482,7 +483,7 @@ METHOD(Mage, mr_setup, bool(Mage this, entity actor)) if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_mage_speed_stop); } if(!actor.damageforcescale) { actor.damageforcescale = (autocvar_g_monster_mage_damageforcescale); } - actor.monster_loot = ITEM_HealthBig; + actor.monster_loot = autocvar_g_monster_mage_loot; actor.monster_attackfunc = M_Mage_Attack; return true; diff --git a/qcsrc/common/monsters/monster/spider.qc b/qcsrc/common/monsters/monster/spider.qc index 90929f5a4..75bd89d6d 100644 --- a/qcsrc/common/monsters/monster/spider.qc +++ b/qcsrc/common/monsters/monster/spider.qc @@ -6,6 +6,7 @@ #ifdef SVQC float autocvar_g_monster_spider_health; +string autocvar_g_monster_spider_loot = "health_medium"; float autocvar_g_monster_spider_damageforcescale = 0.6; float autocvar_g_monster_spider_attack_bite_damage; float autocvar_g_monster_spider_attack_bite_delay; @@ -229,7 +230,7 @@ METHOD(Spider, mr_setup, bool(Spider this, entity actor)) if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_spider_speed_stop); } if(!actor.damageforcescale) { actor.damageforcescale = (autocvar_g_monster_spider_damageforcescale); } - actor.monster_loot = ITEM_HealthMedium; + actor.monster_loot = autocvar_g_monster_spider_loot; actor.monster_attackfunc = M_Spider_Attack; return true; diff --git a/qcsrc/common/monsters/monster/wyvern.qc b/qcsrc/common/monsters/monster/wyvern.qc index 3b48c67e7..d2e567bc1 100644 --- a/qcsrc/common/monsters/monster/wyvern.qc +++ b/qcsrc/common/monsters/monster/wyvern.qc @@ -2,6 +2,7 @@ #ifdef SVQC float autocvar_g_monster_wyvern_health; +string autocvar_g_monster_wyvern_loot = "cells"; float autocvar_g_monster_wyvern_damageforcescale = 0.6; float autocvar_g_monster_wyvern_attack_fireball_damage; float autocvar_g_monster_wyvern_attack_fireball_edgedamage; @@ -173,7 +174,7 @@ METHOD(Wyvern, mr_setup, bool(Wyvern this, entity actor)) if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_wyvern_speed_stop); } if(!actor.damageforcescale) { actor.damageforcescale = (autocvar_g_monster_wyvern_damageforcescale); } - actor.monster_loot = ITEM_Cells; + actor.monster_loot = autocvar_g_monster_wyvern_loot; actor.monster_attackfunc = M_Wyvern_Attack; return true; diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc index 4cb442770..8cb229bb0 100644 --- a/qcsrc/common/monsters/monster/zombie.qc +++ b/qcsrc/common/monsters/monster/zombie.qc @@ -2,6 +2,7 @@ #ifdef SVQC float autocvar_g_monster_zombie_health; +string autocvar_g_monster_zombie_loot = "health_medium"; float autocvar_g_monster_zombie_damageforcescale = 0.55; float autocvar_g_monster_zombie_attack_melee_damage; float autocvar_g_monster_zombie_attack_melee_delay; @@ -159,7 +160,7 @@ METHOD(Zombie, mr_setup, bool(Zombie this, entity actor)) actor.spawnflags |= MONSTER_RESPAWN_DEATHPOINT; - actor.monster_loot = ITEM_HealthMedium; + actor.monster_loot = autocvar_g_monster_zombie_loot; actor.monster_attackfunc = M_Zombie_Attack; StatusEffects_apply(STATUSEFFECT_SpawnShield, actor, actor.spawn_time, 0); actor.respawntime = 0.2; diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 03de664bc..5ce983e18 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -37,23 +37,53 @@ void monsters_setstatus(entity this) STAT(MONSTERS_KILLED, this) = monsters_killed; } -bool autocvar_g_monsters_drop = true; void monster_dropitem(entity this, entity attacker) { - if(!this.candrop || !this.monster_loot || !autocvar_g_monsters_drop) + if(!this.candrop || !autocvar_g_monsters_drop) + return; + + // TODO: mapper customization (different field?) + string itemlist = this.monster_loot; + + if(this.spawnflags & MONSTERFLAG_MINIBOSS) + itemlist = autocvar_g_monsters_miniboss_loot; + + MUTATOR_CALLHOOK(MonsterDropItem, this, itemlist, attacker); + itemlist = M_ARGV(1, string); + + if(itemlist == "") + return; + + RandomSelection_Init(); + FOREACH_WORD(itemlist, true, + { + string item = it; + + FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), + { + if(it.netname == item || (item == "random" && (it.spawnflags & WEP_FLAG_NORMAL) && !(it.spawnflags & WEP_FLAG_HIDDEN) && !(it.spawnflags & WEP_FLAG_SUPERWEAPON))) + RandomSelection_AddEnt(it, 1, 1); + }); + FOREACH(Items, Item_IsDefinitionAllowed(it), + { + if(it.netname == item || (item == "random" && (it.spawnflags & ITEM_FLAG_NORMAL) && !it.instanceOfPowerup)) + RandomSelection_AddEnt(it, 1, 1); + }); + }); + + if(!RandomSelection_chosen_ent) return; entity e = spawn(); - e.itemdef = this.monster_loot; - e.origin = CENTER_OR_VIEWOFS(this); + e.monster_item = true; + ITEM_SET_LOOT(e, true); + e.colormap = this.colormap; + e.itemdef = RandomSelection_chosen_ent; + setorigin(e, CENTER_OR_VIEWOFS(this)); e.velocity = randomvec() * 175 + '0 0 325'; e.lifetime = max(0, autocvar_g_monsters_drop_time); - MUTATOR_CALLHOOK(MonsterDropItem, this, e, attacker); - e = M_ARGV(1, entity); - - if(e && e.itemdef) - Item_Initialise(e); + Item_Initialise(e); } bool monster_facing(entity this, entity targ) @@ -504,20 +534,29 @@ void Monster_Touch(entity this, entity toucher) this.enemy = toucher; } -void Monster_Miniboss_Check(entity this) +void Monster_Miniboss_Setup(entity this) { - if(MUTATOR_CALLHOOK(MonsterCheckBossFlag, this)) + if(this.spawnflags & MONSTERFLAG_RESPAWNED) + { + // already performed initial setup, just reapply statuses as needed + if(this.spawnflags & MONSTERFLAG_MINIBOSS) + this.effects |= EF_RED; return; + } - float chance = random() * 100; + if(MUTATOR_CALLHOOK(MonsterCheckBossFlag, this)) + { + // prevent other code from identifying the monster as a miniboss + this.spawnflags &= ~MONSTERFLAG_MINIBOSS; + return; + } // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss - if ((this.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance)) + if ((this.spawnflags & MONSTERFLAG_MINIBOSS) || ((random() * 100) < autocvar_g_monsters_miniboss_chance)) { GiveResource(this, RES_HEALTH, autocvar_g_monsters_miniboss_healthboost); this.effects |= EF_RED; - if(!this.weapon) - this.weapon = WEP_VORTEX.m_id; + this.spawnflags |= MONSTERFLAG_MINIBOSS; // identifier for other code } } @@ -1068,12 +1107,6 @@ void Monster_Dead(entity this, entity attacker, float gibbed) Monster mon = this.monsterdef; mon.mr_death(mon, this); - - if(this.candrop && this.weapon && autocvar_g_monsters_drop) - { - .entity weaponentity = weaponentities[0]; // TODO: unhardcode - W_ThrowNewWeapon(this, this.weapon, 0, this.origin, randomvec() * 150 + '0 0 325', weaponentity); - } } void Monster_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) @@ -1373,9 +1406,10 @@ bool Monster_Spawn_Setup(entity this) if(!this.attack_range) { this.attack_range = autocvar_g_monsters_attack_range; } if(!this.damageforcescale) { this.damageforcescale = autocvar_g_monsters_damageforcescale; } + Monster_Miniboss_Setup(this); + if(!(this.spawnflags & MONSTERFLAG_RESPAWNED)) { - Monster_Miniboss_Check(this); SetResourceExplicit(this, RES_HEALTH, GetResource(this, RES_HEALTH) * MONSTER_SKILLMOD(this)); if(!this.skin) diff --git a/qcsrc/common/monsters/sv_monsters.qh b/qcsrc/common/monsters/sv_monsters.qh index 49c560e25..e367fb419 100644 --- a/qcsrc/common/monsters/sv_monsters.qh +++ b/qcsrc/common/monsters/sv_monsters.qh @@ -20,6 +20,8 @@ bool autocvar_g_monsters_owners; bool autocvar_g_monsters_playerclip_collisions; float autocvar_g_monsters_miniboss_chance; float autocvar_g_monsters_miniboss_healthboost; +string autocvar_g_monsters_miniboss_loot = "vortex"; +bool autocvar_g_monsters_drop = true; float autocvar_g_monsters_drop_time; float autocvar_g_monsters_spawnshieldtime; bool autocvar_g_monsters_quake_resize = true; @@ -56,6 +58,7 @@ int monsters_killed; .float stopspeed; .int oldskin; .string mdl_dead; // dead model for goombas +.bool monster_item; // identifier for dropped monster loot TODO: generic identifiers? ok_item exists too! #define MONSTER_SKILLMOD(mon) (0.5 + mon.monster_skill * ((1.2 - 0.3) / 10)) diff --git a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc index d6884dff5..67fec6c6d 100644 --- a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc +++ b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc @@ -108,9 +108,7 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName) MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem) { - entity item = M_ARGV(1, entity); - - item.itemdef = ITEM_VaporizerCells; + M_ARGV(1, string) = "vaporizer_cells"; } MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn) diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc index 53eb25575..619c393e4 100644 --- a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc @@ -108,10 +108,13 @@ MUTATOR_HOOKFUNCTION(ok, PlayerDies) MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) { entity mon = M_ARGV(0, entity); - entity item = M_ARGV(1, entity); entity frag_attacker = M_ARGV(2, entity); + entity item = spawn(); ok_DropItem(mon, frag_attacker, item); + Item_Initialise(item); + + M_ARGV(1, string) = ""; // item drops handled } MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon) diff --git a/qcsrc/server/mutators/events.qh b/qcsrc/server/mutators/events.qh index 2e911e4c3..19f84a3a8 100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@ -375,11 +375,11 @@ MUTATOR_HOOKABLE(MonsterRespawn, EV_MonsterRespawn); /** called when a monster is dropping loot */ #define EV_MonsterDropItem(i, o) \ /* monster */ i(entity, MUTATOR_ARGV_0_entity) \ - /* item (can be removed or changed) */ i(entity, MUTATOR_ARGV_1_entity) \ - /**/ o(entity, MUTATOR_ARGV_1_entity) \ + /* list of items to drop */ i(string, MUTATOR_ARGV_1_string) \ + /**/ o(string, MUTATOR_ARGV_1_string) \ /* attacker */ i(entity, MUTATOR_ARGV_2_entity) \ /**/ -.entity monster_loot; +.string monster_loot; MUTATOR_HOOKABLE(MonsterDropItem, EV_MonsterDropItem); /** @@ -409,7 +409,10 @@ MUTATOR_HOOKABLE(MonsterFindTarget, EV_NO_ARGS); /**/ MUTATOR_HOOKABLE(MonsterValidTarget, EV_MonsterValidTarget); -/** called to change a random monster to a miniboss */ +/** + * called when checking if a monster should be a miniboss + * return true to prevent the monster from becoming a miniboss + */ #define EV_MonsterCheckBossFlag(i, o) \ /** monster */ i(entity, MUTATOR_ARGV_0_entity) \ /**/ -- 2.39.5