Skip to content

Custom Room Modifiers

Brandon edited this page Jun 21, 2026 · 2 revisions

Here we will make a RoomModifier. All RoomModifiers are a subclass of RoomStateModifierBase along with implementing one or more IRoomStateXXXModifier interfaces to implement functionality.

Overview

The RoomModifer we will be making is RoomStateDamagePerAttackModifier. This room modifier when applied to a character will increase the attack of all other units in the room by the characters attack stat.

We will make this configurable

  1. param_int will specify a flat damage boost to give to characters.

  2. param_float will specify a multiplier to the units attack to give.

  3. param_int_2 will specify whether to use the base attack stat or the current attack of the character. (0: current attack, 1: base attack)

Code

   public class RoomStateDamagePerAttackModifier : RoomStateModifierBase, IRoomStateDamageModifier
   {
       enum AttackShareType
       {
           CurrentAttack = 0,
           BaseAttack = 1,
       }

       CharacterState? owner;
       private float additionalDamageMultiplier;
       private int additionalDamage;
       private AttackShareType attackShareType;

       public override void Initialize(RoomModifierData roomModifierData, SaveManager saveManager)
       {
           base.Initialize(roomModifierData, saveManager);
           additionalDamage = roomModifierData.GetParamInt();
           attackShareType = (AttackShareType)roomModifierData.GetParamInt2();
           additionalDamageMultiplier = roomModifierData.GetParamFloat();
       }

       public override void OnRoomModifierAttachedToOwner(CharacterState ownerCharacterState)
       {
           owner = ownerCharacterState;
       }

       public int GetModifiedAttackDamage(Damage.Type damageType, CharacterState attackerState, bool requestingForCharacterStats, ICoreGameManagers coreGameManagers)
       {
           if (requestingForCharacterStats)
           {
               return GetDynamicInt(attackerState);
           }
           return 0;
       }

        public override int GetDynamicInt(CharacterState characterContext)
        {
            if (owner == null || characterContext == owner || characterContext.GetSpawnPoint() == null)
                return 0;

            if (characterContext.GetTeamType() == owner.GetTeamType())
            {
                return Mathf.FloorToInt(GetBaseDamage() * additionalDamageMultiplier) + additionalDamage;
            }

            return 0;
        }

       private int GetBaseDamage()
       {
           switch (attackShareType)
           {
               case AttackShareType.BaseAttack:
                   return owner?.GetUnbuffedAttackDamage() ?? 0;
               case AttackShareType.CurrentAttack:
                   return owner?.GetUnmodifiedAttackDamage() ?? 0;
           }
           return 0;
       }

       public int GetModifiedMagicPowerDamage(ICoreGameManagers coreGameManagers)
       {
           return 0;
       }
   }

Class Declaration

public class RoomStateDamagePerAttackModifier : RoomStateModifierBase, IRoomStateDamageModifier

IRoomStateDamageModifier is a interface that allows a RoomModifier to adjust attack damage, magic power for the floor, as well as damage dealt by the unit for any damage type.

Initialize

This function is called to set up the state of the RoomModifier. It is important to be sure to call the base classes Initialize function as that handles initializing all of the fields from the RoomModifierData configured.

OnRoomModifierAttachedToOwner

This function is defined in RoomStateModifierBase It is called when the RoomModifier is attached to its owner. We are overriding this function to keep a reference to the owner of the RoomModifier as there is no function defined in RoomStateModifierBase to get the owner.

We will need the owner of the RoomModifier to get the attack we need to add to the other units.

GetModifiedAttackDamage

This function is one of the interface functions of IRoomStateDamageModifier. It returns the amount of attack damage to add to the character.

Here we only returned the amount of modified damage when the function is called with requestingForCharacterStats is true. This value will be added to the characters attack.

Otherwise, the extra damage will be applied multiple times once when the character attacks and then again when the damage is actually dealt to the unit, it will also increase the damage dealt by status effects like spikes whose damage is attributed to the unit with the status effect (This is also how units dying to spikes damage triggers slay on the defender).

This function also allows modifying damage of a specific type, via checking damageType for a specific Damage.Type enum and returning a non-zero value to increase or decrease that type of damage.

GetDynamicInt

This function returns a dynamic integer parameter usually the amount current applied by the room modifier. When you use[dynamicint] in descriptions it is replaced by the return value of this function.

Here we return the exact amount of attack damage.

  1. We perform some failure checks with the first if statement

  2. If the owner is null (This can happen if someone mistakenly uses this effect in a Room card's effect)

  3. If the current character we are getting modified attack damage for is the owner of the RoomModifier itself. Otherwise we'd be doubling this units attack.

  4. Error checking If the character doesn't have a spawn point. Then its not on this floor and not eligible. This shouldn't really happen however, but you should still do it.

  5. We check the Team the character is on and it must be the same as the owners. This is a defensive check, Technically RoomModifiers applied to characters will only have the functions called for units on the same team i.e. You can't have a RoomModifier that does something to the opposing team.

  6. We then call GetBaseDamage() multiply by the multiplier specified in the effect and add additionalDamage that is specified by the effect.

GetBaseDamage

Here we compute the attack that we are applying to other units on the same team based on param_int_2. We either call GetUnbuffedAttackDamage or GetUnmodifiedAttackDamage

Getting a Units Attack.

Here is an opportunity to explain the different functions to get the Character's stats.

  • GetUnbuffedAttackDamage gets the base attack of the card not considering status effects like Rage and Valor or RoomModifiers. This should be the same value as the card as shown in the deck during battle.

  • GetUnmodifiedAttackDamage This is GetUnbuffedAttackDamage + DamageFromBuffs but not damage gained from RoomModifiers.

  • GetAttackDamage return the full attack damage of the character, this will be the same number as displayed on the character. In this case this would also consider damage from RoomModifiers.

If you are wondering why we don't use GetAttackDamage here, The reason is simple. If we had two of these units on the floor the attack gains would be infinity. Each time the attack is queried it would continuously add the attack from the other ones.

To keep things simple we only allow transferring the attack stat without the RoomModifier adjustments.

Example

If i had a unit with 10 damage, 6 rage, and Deranged Brute was on the same floor (the effect is a RoomModifiier that modifies Rage to add 3 attack per stack).

  • GetUnbuffedAttackDamage would return 10.

  • GetUnmodifiedAttackDamage would return 22.

  • GetAttackDamage would return 40.

GetModifiedMagicPowerDamage

We return 0 here, this is another function required for IRoomStateDamageModifier, we could return a value to adjust the magic power damage on the floor.

Testing

We can just create a RoomModifierData via json and add it to an existing character. Here is JSON that does so with Guardian Totem.

  "characters": [
    {
      "id": "MonsterTotemHealing",
      "override": "replace",
      "room_modifiers": [ "@PassAttack" ]
    }
  ],
  "room_modifiers": [
    {
      "id": "PassAttack",
      "name": "@RoomStateDamagePerAttackModifier",
      "titles": {
        "english": "Empowering"
      },
      "descriptions": {
        "english": "[attack] is shared among friendly units."
      },
      "param_int": 0,
      "param_float": 1,
      "param_int_2": 0
    }
  ]

Remember that param_int specifies the amount of flat damage to add.

param_float adjusts the multiplier applied to the other units

param_int_2 specifies we want the current attack (base attack + buffs)

We also give the RoomModifierData a title for the tooltip. Otherwise the game will attempt to use the classes full name as a localization key and try to find the localization for that which will fail.

You can play around with the values to ensure every configuration option is working.

Clone this wiki locally