diff --git a/data/json/effects.json b/data/json/effects.json index 154e692dde1ef..0ca8284fdfb8b 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -1773,6 +1773,26 @@ "desc": [ "You've been struck by lightning, and feel... different." ], "base_mods": { "speed_mod": [ 50 ] } }, + { + "type": "effect_type", + "id": "grown_of_fuse", + "name": [ "Grown of Fusion" ], + "desc": [ "AI effect to increase stats after fusing with another critter. 1 stack means one absorbed max_hp." ], + "//": "stats modified by the zombie_fuse special attack.", + "//1": "scaling:", + "//2": "/48 HP: +0,5 melee_skill, +0,5 dodge_skill (unexpected movement), +5 Speed (bigger steps&more to attack with), +4 bash", + "//3": "/160 HP: +1 size", + "max_intensity": 480, + "base_mods": { "hit_mod": [ 0.0104 ], "dodge_mod": [ 0.0104 ], "speed_mod": [ 0.1042 ], "bash_mod": [ 0.08333 ] }, + "scaling_mods": { + "hit_mod": [ 0.0104 ], + "dodge_mod": [ 0.0104 ], + "speed_mod": [ 0.1042 ], + "bash_mod": [ 0.08333 ], + "growth_mod": [ 0.00625 ] + }, + "int_decay_step": 0 + }, { "type": "effect_type", "id": "has_og_comm_freq" diff --git a/data/json/monsters/monsters.json b/data/json/monsters/monsters.json index 63ed07025fe08..61ef43553773f 100644 --- a/data/json/monsters/monsters.json +++ b/data/json/monsters/monsters.json @@ -532,6 +532,56 @@ "FILTHY" ] }, + { + "id": "mon_devourer", + "type": "MONSTER", + "name": "dissoluted devourer", + "description": "Human bodies fused together into a colossus with heads and limbs sticking out of its bloated body. You may have trouble estimating its healthiness and its capabilities might change.", + "default_faction": "zombie", + "bodytype": "human", + "species": [ "ZOMBIE", "HUMAN" ], + "diff": 8, + "volume": "92500 ml", + "weight": 120000, + "hp": 112, + "speed": 60, + "material": [ "flesh" ], + "symbol": "Z", + "color": "dark_gray_yellow", + "aggression": 100, + "morale": 100, + "melee_skill": 4, + "melee_dice": 3, + "melee_dice_sides": 3, + "melee_cut": 0, + "armor_bash": 5, + "armor_cut": 5, + "vision_night": 3, + "harvest": "zombie", + "special_attacks": [ [ "GRAB", 7 ], [ "scratch", 20 ], [ "ZOMBIE_FUSE", 80 ] ], + "death_drops": { + "subtype": "collection", + "groups": [ "default_zombie_death_drops", "default_zombie_death_drops", "default_zombie_death_drops" ] + }, + "death_function": [ "SPLATTER", "NORMAL" ], + "flags": [ + "SEES", + "HEARS", + "SMELLS", + "STUMBLES", + "WARM", + "BASHES", + "GROUP_BASH", + "POISON", + "NO_BREATHE", + "REVIVES", + "BILE_BLOOD", + "PUSH_MON", + "PATH_AVOID_DANGER_1", + "FILTHY", + "REGENERATES_1" + ] + }, { "id": "mon_breather", "type": "MONSTER", @@ -4732,6 +4782,7 @@ "death_drops": "default_zombie_death_drops", "death_function": [ "NORMAL" ], "burn_into": "mon_zombie_scorched", + "upgrades": { "half_life": 23, "into": "mon_devourer" }, "flags": [ "SEES", "HEARS", diff --git a/src/creature.cpp b/src/creature.cpp index 6dc2b455373d3..42409df7f2802 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -1473,6 +1473,7 @@ void Creature::set_armor_cut_bonus( int ncutarm ) armor_cut_bonus = ncutarm; } + void Creature::set_speed_base( int nspeed ) { speed_base = nspeed; @@ -1485,6 +1486,7 @@ void Creature::set_dodge_bonus( float ndodge ) { dodge_bonus = ndodge; } + void Creature::set_block_bonus( int nblock ) { block_bonus = nblock; diff --git a/src/creature.h b/src/creature.h index 1f328609ff683..fc9b36ffee126 100644 --- a/src/creature.h +++ b/src/creature.h @@ -50,11 +50,11 @@ struct pathfinding_settings; struct trap; enum m_size : int { - MS_TINY = 0, // Squirrel - MS_SMALL, // Dog - MS_MEDIUM, // Human - MS_LARGE, // Cow - MS_HUGE // TAAAANK + MS_TINY = 1, // Squirrel + MS_SMALL = 2, // Dog + MS_MEDIUM = 3, // Human + MS_LARGE = 4, // Cow + MS_HUGE = 5 // TAAAANK }; enum FacingDirection { @@ -342,7 +342,7 @@ class Creature /** Removes a listed effect. bp = num_bp means to remove all effects of * a given type, targeted or untargeted. Returns true if anything was * removed. */ - bool remove_effect( const efftype_id &eff_id, body_part bp = num_bp ); + virtual bool remove_effect( const efftype_id &eff_id, body_part bp = num_bp ); /** Remove all effects. */ void clear_effects(); /** Check if creature has the matching effect. bp = num_bp means to check if the Creature has any effect @@ -745,7 +745,6 @@ class Creature int armor_bash_bonus; int armor_cut_bonus; - int speed_base; // only speed needs a base, the rest are assumed at 0 and calculated off skills int speed_bonus; diff --git a/src/effect.cpp b/src/effect.cpp index 5d47221b51cb0..b09d2b01c44ed 100644 --- a/src/effect.cpp +++ b/src/effect.cpp @@ -343,6 +343,13 @@ bool effect_type::load_mod_data( JsonObject &jo, const std::string &member ) extract_effect( j, mod_data, "healing_head", member, "HEAL_HEAD", "amount" ); extract_effect( j, mod_data, "healing_torso", member, "HEAL_TORSO", "amount" ); + // creature stats mod + extract_effect( j, mod_data, "dodge_mod", member, "DODGE", "min" ); + extract_effect( j, mod_data, "hit_mod", member, "HIT", "min" ); + extract_effect( j, mod_data, "bash_mod", member, "BASH", "min" ); + extract_effect( j, mod_data, "cut_mod", member, "CUT", "min" ); + extract_effect( j, mod_data, "growth_mod", member, "GROWTH", "min" ); + return true; } else { return false; @@ -465,6 +472,11 @@ bool effect_type::load_decay_msgs( JsonObject &jo, const std::string &member ) return false; } +int effect_type::get_max_intensity() const +{ + return max_intensity; +} + effect effect::null_effect; bool effect::is_null() const diff --git a/src/effect.h b/src/effect.h index b5a1c9941e7dd..ff70fbc7c24d5 100644 --- a/src/effect.h +++ b/src/effect.h @@ -82,6 +82,9 @@ class effect_type /** Registers the effect in the global map */ static void register_ma_buff_effect( const effect_type &eff ); + /** Returns the maximum intensity of this type. */ + int get_max_intensity() const; + protected: int max_intensity; int max_effective_intensity; diff --git a/src/monattack.cpp b/src/monattack.cpp index b071220b18cc8..e12577d931e29 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -135,6 +135,7 @@ const efftype_id effect_fungus( "fungus" ); const efftype_id effect_glowing( "glowing" ); const efftype_id effect_got_checked( "got_checked" ); const efftype_id effect_grabbed( "grabbed" ); +const efftype_id effect_grown_of_fuse( "grown_of_fuse" ); const efftype_id effect_grabbing( "grabbing" ); const efftype_id effect_has_bag( "has_bag" ); const efftype_id effect_infected( "infected" ); @@ -5263,6 +5264,36 @@ bool mattack::stretch_attack( monster *z ) return true; } +bool mattack::zombie_fuse( monster *z ) +{ + monster *critter = nullptr; + for( const tripoint &p : g->m.points_in_radius( z->pos(), 1 ) ) { + critter = g->critter_at( p ); + if( critter != nullptr && critter->faction == z->faction + && critter != z && critter->get_size() <= z->get_size() ) { + break; + } + } + + if( critter == nullptr || + ( z->get_hp() + critter->get_hp() > z->get_hp_max() + + effect_grown_of_fuse.obj().get_max_intensity() ) ) { + return false; + } + if( g->u.sees( *z ) ) { + add_msg( _( "The %1$s fuses with the %2$s." ), + critter->name(), + z->name() ); + } + z->moves -= 200; + z->add_effect( effect_grown_of_fuse, 10_days, num_bp, true, + critter->get_hp_max() + z->get_effect( effect_grown_of_fuse ).get_intensity() ); + z->heal( critter->get_hp(), true ); + critter->death_drops = false; + critter->die( z ); + return true; +} + bool mattack::doot( monster *z ) { z->moves -= 300; diff --git a/src/monattack.h b/src/monattack.h index 8ccc9f87567b5..2a261e58075b0 100644 --- a/src/monattack.h +++ b/src/monattack.h @@ -99,6 +99,7 @@ bool kamikaze( monster *z ); bool grenadier( monster *z ); bool grenadier_elite( monster *z ); bool doot( monster *z ); +bool zombie_fuse( monster *z ); void taze( monster *z, Creature *target ); void rifle( monster *z, Creature *target ); // Automated M4 diff --git a/src/monster.cpp b/src/monster.cpp index dc83c3b1cf27a..a7d54c3fd3a10 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -212,6 +212,8 @@ monster::monster() upgrade_time = -1; last_updated = 0; biosig_timer = -1; + + monster::reset_bonuses(); } monster::monster( const mtype_id &id ) : monster() @@ -620,6 +622,10 @@ int monster::print_info( const catacurses::window &w, int vStart, int vLines, in mvwprintz( w, point( column, vStart++ ), c_white, _( "Rider: %s" ), mounted_player->disp_name() ); } + if( effect_cache[MODIFIED] ) { + wprintz( w, c_light_gray, _( " It is %s." ), size_names.at( get_size() ) ); + } + std::vector lines = foldstring( type->get_description(), getmaxx( w ) - 1 - column ); int numlines = lines.size(); for( int i = 0; i < numlines && vStart <= vEnd; i++ ) { @@ -1685,6 +1691,29 @@ void monster::add_effect( const efftype_id &eff_id, const time_duration dur, bod { // Effects are not applied to specific monster body part Creature::add_effect( eff_id, dur, num_bp, permanent, intensity, force, deferred ); + + effect_cache[MODIFIED] = effect_cache[MODIFIED] | effect_is_modifier_enabled( get_effect( + eff_id ) ); +} + +bool monster::remove_effect( const efftype_id &eff_id, body_part bp ) +{ + bool modif = effect_is_modifier_enabled( get_effect( eff_id ) ); + bool rtrn = Creature::remove_effect( eff_id, bp ); + + if( modif && effect_cache[MODIFIED] ) { + modif = false; + for( const auto &ef : *effects ) { + modif |= effect_is_modifier_enabled( ef.second.at( num_bp ) ); + } + effect_cache[MODIFIED] = modif; + } + return rtrn; +} + +bool monster::effect_is_modifier_enabled( const effect &eff ) +{ + return eff.get_mod( "GROWTH" ) != 0; } std::string monster::get_effect_status() const @@ -2242,6 +2271,17 @@ bool monster::check_mech_powered() const return true; } +int monster::get_effect_bonus( std::string arg, bool reduced ) const +{ + int rtrn = 0; + for( const auto effbody : *effects ) { + for( const auto ef : effbody.second ) { + rtrn += ef.second.get_mod( arg, reduced ); + } + } + return rtrn; +} + void monster::drop_items_on_death() { if( is_hallucination() ) { @@ -2293,6 +2333,10 @@ void monster::process_one_effect( effect &it, bool is_new ) }; mod_speed_bonus( get_effect( "SPEED", reduced ) ); + mod_dodge_bonus( get_effect( "DODGE", reduced ) ); + mod_hit_bonus( get_effect( "HIT", reduced ) ); + mod_bash_bonus( get_effect( "BASH", reduced ) ); + mod_cut_bonus( get_effect( "CUT", reduced ) ); int val = get_effect( "HURT", reduced ); if( val > 0 ) { @@ -2528,17 +2572,29 @@ field_type_id monster::gibType() const m_size monster::get_size() const { - return type->size; + if( effect_cache[MODIFIED] ) { + return m_size( type->size + get_effect_bonus( "GROWTH" ) ); + } else { + return type->size; + } } units::mass monster::get_weight() const { - return type->weight; + if( effect_cache[MODIFIED] ) { + return units::operator*( type->weight, get_size() / type->size ); + } else { + return type->weight; + } } units::volume monster::get_volume() const { - return type->volume; + if( effect_cache[MODIFIED] ) { + return units::operator*( type->volume, get_size() / type->size ); + } else { + return type->volume; + } } void monster::add_msg_if_npc( const std::string &msg ) const diff --git a/src/monster.h b/src/monster.h index 584434b3d351a..fce1827470f23 100644 --- a/src/monster.h +++ b/src/monster.h @@ -68,6 +68,7 @@ enum monster_effect_cache_fields { MOVEMENT_IMPAIRED = 0, FLEEING, VISION_IMPAIRED, + MODIFIED, // Uses data hold by effects instead of itself. NUM_MEFF }; @@ -320,6 +321,10 @@ class monster : public Creature void add_effect( const efftype_id &eff_id, time_duration dur, body_part bp = num_bp, bool permanent = false, int intensity = 0, bool force = false, bool deferred = false ) override; + /** Same for removing effects.*/ + bool remove_effect( const efftype_id &eff_id, body_part bp = num_bp ) override; + /** Returns true for an effect, whoose funcionality is enabled through effect_cache[MODIFIED] to reduce overhead.*/ + bool effect_is_modifier_enabled( const effect &eff ); /** Returns a std::string containing effects for descriptions */ std::string get_effect_status() const; @@ -378,7 +383,7 @@ class monster : public Creature /** Resets stats, and applies effects in an idempotent manner */ void reset_stats() override; - void die( Creature *killer ) override; //this is the die from Creature, it calls kill_mon + void die( Creature *killer ) override; void drop_items_on_death(); // Other @@ -396,6 +401,9 @@ class monster : public Creature bool use_mech_power( int amt ); bool check_mech_powered() const; int mech_str_addition() const; + // get an value from the effect data. + int get_effect_bonus( std::string arg, bool reduced = false ) const; + /** * Makes monster react to heard sound * diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 34848cde5a329..f8780c515420c 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -546,6 +546,7 @@ void MonsterGenerator::init_attack() add_hardcoded_attack( "GRAB", mattack::grab ); add_hardcoded_attack( "GRAB_DRAG", mattack::grab_drag ); add_hardcoded_attack( "DOOT", mattack::doot ); + add_hardcoded_attack( "ZOMBIE_FUSE", mattack::zombie_fuse ); } void MonsterGenerator::init_defense()