Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
42f8314
Cleanse (cont.)
AdamPlenty Nov 30, 2025
62c4d3d
Update magic.cfg
AdamPlenty Nov 30, 2025
d6fdf20
Update creature.cfg
AdamPlenty Nov 30, 2025
7e69b91
Update creature.cfg
AdamPlenty Nov 30, 2025
dc526e5
Update creature.cfg
AdamPlenty Nov 30, 2025
63452bf
Update thing_creature.c
AdamPlenty Nov 30, 2025
a8f1f8b
Update magic.cfg
AdamPlenty Nov 30, 2025
bfb8e0b
Update magic.cfg
AdamPlenty Nov 30, 2025
f738daf
Not needed.
AdamPlenty Nov 30, 2025
84a9213
Update magic.cfg
AdamPlenty Nov 30, 2025
b3a1e09
Does this help?
AdamPlenty Nov 30, 2025
e4b1ce8
Update creature.cfg
AdamPlenty Nov 30, 2025
7f2d06e
Now it works!
AdamPlenty Nov 30, 2025
b5de5da
Set Spell Block flags.
AdamPlenty Nov 30, 2025
a066fc9
Not needed.
AdamPlenty Nov 30, 2025
76e70e9
Update thing_creature.c
AdamPlenty Nov 30, 2025
e306d89
Fixed immediate termination
AdamPlenty Nov 30, 2025
2f396b1
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Nov 30, 2025
d5bb9a0
Block spells
AdamPlenty Nov 30, 2025
bcfc8fa
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Nov 30, 2025
d214e82
Take spell from instance info
AdamPlenty Nov 30, 2025
c76fa1d
User param1 for validation spell id
AdamPlenty Nov 30, 2025
4991e53
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 1, 2025
2491866
cleanse_flags creature control field
AdamPlenty Dec 1, 2025
cab24e6
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 1, 2025
c555929
Don't cure or block MAD_KILLING or TIMEBOMB
AdamPlenty Dec 1, 2025
1014206
Fixed icon positioning.
AdamPlenty Dec 2, 2025
7b7de8b
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 3, 2025
152c121
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 4, 2025
a18b676
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 8, 2025
876dcda
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 10, 2025
833a329
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 10, 2025
f378774
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 11, 2025
a389514
Merge branch 'master' into pr/4381
Loobinex Dec 11, 2025
0684839
Creatures self-Cleanse for Freeze
AdamPlenty Dec 12, 2025
3ef74aa
Make it configurable
AdamPlenty Dec 12, 2025
09dae08
Update creature.cfg
AdamPlenty Dec 12, 2025
6c25992
Update creature.cfg
AdamPlenty Dec 12, 2025
edbff96
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 12, 2025
3be44c5
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 13, 2025
2c4000d
Merge branch 'master' into pr/4381
Loobinex Dec 13, 2025
038ee65
Configurable effect
AdamPlenty Dec 13, 2025
ad60ec1
Purple stars effect
AdamPlenty Dec 13, 2025
0ebae48
Update effects.toml
AdamPlenty Dec 13, 2025
803e7e4
Update magic.cfg
AdamPlenty Dec 13, 2025
81ebe35
Update effects.toml
AdamPlenty Dec 13, 2025
b5b1d6c
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 14, 2025
96c4989
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 16, 2025
7d4fb35
Merge remote-tracking branch 'upstream/master' into Cleanse
AdamPlenty Dec 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions config/fxdata/creature.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ PostalPriority = 0
; RANGED_BUFF can be applied to another friendly creature.
; NEEDS_TARGET Cannot be used in possession without a target to cast it on.
; DISARMING allows the instance to be used against traps.
; WHILE_FROZEN allows the instance to be used while the creature is frozen (under the Freeze spell).
Properties =
; Function used as the instance action, and its parameters.
Function = none 0 0
Expand Down Expand Up @@ -1114,6 +1115,25 @@ PrimaryTarget = 3
Properties = DESTRUCTIVE MELEE_ATTACK DISPLAY_SWIPE
Function = creature_fire_shot SHOT_CRIPPLE 0

[instance55]
Name = CLEANSE
Time = 10
ActionTime = 6
ResetTime = 375
FPTime = 5
FPActionTime = 3
FPResetTime = 300
FPInstantCast = 1
ForceVisibility = 10
TooltipTextID = 1067
SymbolSprites = 798
Graphics = CASTSPELL
Properties = SELF_BUFF WHILE_FROZEN
PrimaryTarget = 3
Function = creature_cast_spell SPELL_CLEANSE 0
ValidateSourceFunc = validate_source_even_in_prison 0 0
ValidateTargetFunc = validate_target_requires_cleansing 32 0

[job0]
; Empty job, indicates no job assigned.
Name = NULL
Expand Down
30 changes: 29 additions & 1 deletion config/fxdata/effects.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5301,4 +5301,32 @@ TransformModel = 0
LightRadius = 1
LightIntensity = 0
LightFlags = 0
AffectedByWind = 1
AffectedByWind = 1

[effectElement124]
Name = "EFFECTELEMENT_CLEANSE"
DrawClass = 2
MoveType = 5
Unanimated = 0
Lifespan = [-1,-1]
AnimationId = 907
SpriteSize = [96,150]
AnimateOnce = 0
SpriteSpeed = [256,256]
AnimateOnFloor = true
Unshaded = true
Transparent = 1
ThroughWalls = 0
SizeChange = 0
FallAcceleration = 0
InertiaFloor = 0
InertiaAir = 0
SubeffectModel = 0
SubeffectDelay = 0
Movable = false
Impacts = false
TransformModel = 0
LightRadius = 0
LightIntensity = 0
LightFlags = 0
AffectedByWind = 0
16 changes: 16 additions & 0 deletions config/fxdata/magic.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ SpellPower = NOPOWER
; 32768: TELEPORT - If self-cast, jumps to its next objective or place to fulfill its needs. If cast on a target, jumps it to its lair or flee point.
; 65536: TIMEBOMB - Sets a countdown on the target, making it move toward enemies to explode on impact, dealing damage from the linked shot.
; 131072: WIND - Not functional on spells, exists only for immunity.
; 262144: CLEANSE - Cures effects specified in 'CleanseFlags'.
; 524288: SPELL_BLOCKS - Prevents effects from affecting the creature. Must be combined with 'CleanseFlags' to function, has no effect on its own.
SpellFlags = 0
; Spell effects to remove from the target. Use the same list as 'SpellFlags'. Accepts both names and numbers.
CleanseFlags = 0
Expand Down Expand Up @@ -357,6 +359,20 @@ Duration = 160
SelfCasted = 1 978 1
SymbolSprites = 768 770

[spell32]
; Cures all negative effect and blocks them for a short duration.
Name = SPELL_CLEANSE
CastAtThing = 0
ShotModel = NOSHOT
SpellPower = NOPOWER
SpellFlags = SPELL_BLOCKS
AuraEffect = EFFECTELEMENT_CLEANSE
AuraDuration = 10
CleanseFlags = SLOW DISEASE CHICKEN FREEZE FEAR
Duration = 100
SelfCasted = 1 161 1
SymbolSprites = 796 798

; Shots types.

[shot0]
Expand Down
1 change: 1 addition & 0 deletions src/config_creature.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const struct NamedCommand creaturetype_instance_properties[] = {
{"DISPLAY_SWIPE", InstPF_UsesSwipe},
{"RANGED_BUFF", InstPF_RangedBuff},
{"NEEDS_TARGET", InstPF_NeedsTarget},
{"WHILE_FROZEN", InstPF_AllowWhileFrozen},
{NULL, 0},
};

Expand Down
1 change: 1 addition & 0 deletions src/config_creature.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ enum InstancePropertiesFlags {
InstPF_UsesSwipe = 0x0200,
InstPF_RangedBuff = 0x0400,
InstPF_NeedsTarget = 0x0800,
InstPF_AllowWhileFrozen = 0x1000,
};

enum CreatureDeathKind {
Expand Down
1 change: 1 addition & 0 deletions src/config_magic.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const struct NamedCommand spell_effect_flags[] = {
{"TELEPORT", CSAfF_Teleport},
{"TIMEBOMB", CSAfF_Timebomb},
{"WIND", CSAfF_Wind},
{"SPELL_BLOCKS", CSAfF_SpellBlocks},
{NULL, 0},
};

Expand Down
1 change: 1 addition & 0 deletions src/config_magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ enum CreatureSpellAffectedFlags {
CSAfF_Teleport = 0x008000,
CSAfF_Timebomb = 0x010000,
CSAfF_Wind = 0x020000,
CSAfF_SpellBlocks = 0x040000,
};

enum SpellPropertiesFlags {
Expand Down
1 change: 1 addition & 0 deletions src/creature_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ struct CreatureControl {
long turns_at_job;
short blocking_door_id;
unsigned char move_flags;
unsigned long cleanse_flags;

union // Union on diggers, heroes and normal creatures
{
Expand Down
41 changes: 40 additions & 1 deletion src/creature_instances.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const struct NamedCommand creature_instances_validate_func_type[] = {
{"validate_target_benefits_from_wind", 10},
{"validate_target_non_idle", 11},
{"validate_target_takes_gas_damage", 12},
{"validate_target_requires_cleansing", 13},
{NULL, 0},
};

Expand All @@ -143,6 +144,7 @@ Creature_Validate_Func creature_instances_validate_func_list[] = {
validate_target_benefits_from_wind,
validate_target_non_idle,
validate_target_takes_gas_damage,
validate_target_requires_cleansing,
NULL,
};

Expand Down Expand Up @@ -1146,14 +1148,18 @@ TbBool validate_source_basic

if (!creature_instance_is_available(source, inst_idx) ||
!creature_instance_has_reset(source, inst_idx) ||
creature_under_spell_effect(source, CSAfF_Freeze) ||
creature_is_fleeing_combat(source) || creature_under_spell_effect(source, CSAfF_Chicken) ||
creature_is_being_unconscious(source) || creature_is_dying(source) ||
thing_is_picked_up(source) || creature_is_being_dropped(source) ||
creature_is_being_sacrificed(source) || creature_is_being_summoned(source))
{
return false;
}
if (creature_under_spell_effect(source, CSAfF_Freeze))
{
struct InstanceInfo* inst_inf = creature_instance_info_get(inst_idx);
return flag_is_set(inst_inf->instance_property_flags, InstPF_AllowWhileFrozen);
}

return true;
}
Expand Down Expand Up @@ -1825,4 +1831,37 @@ void script_set_creature_instance(ThingModel crmodel, short slot, int instance,
}
}

TbBool validate_target_requires_cleansing
(
struct Thing *source,
struct Thing *target,
CrInstance inst_idx,
int32_t param1,
int32_t param2
)
{
if (!validate_target_basic(source, target, inst_idx, param1, param2) || creature_is_being_unconscious(target) ||
!creature_requires_cleansing(target, param1))
{
return false;
}

if (source->index == target->index)
{
// Special case. The creature is always allowed to cleanse itself even if
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think creatures being tortured should use this.

// it's being tortured or imprisoned.
return true;
}
else
{
if (creature_is_being_tortured(target) || creature_is_kept_in_prison(target) ||
creature_is_being_tortured(source) || creature_is_kept_in_prison(source))
{
return false;
}
}

return true;
}

/******************************************************************************/
3 changes: 3 additions & 0 deletions src/creature_instances.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ enum CreatureInstances {
CrInst_RANGED_SPEED,
CrInst_RANGED_ARMOUR,
CrInst_RANGED_REBOUND,
CrInst_CRIPPLE,
CrInst_CLEANSE,
CrInst_LISTEND,
};

Expand Down Expand Up @@ -179,6 +181,7 @@ TbBool validate_target_benefits_from_wind(struct Thing *source, struct Thing *ta
TbBool validate_target_benefits_from_healing(struct Thing *source, struct Thing *target, CrInstance inst_idx, int32_t param1, int32_t param2);
TbBool validate_target_non_idle(struct Thing* source, struct Thing* target, CrInstance inst_idx, int32_t param1, int32_t param2);
TbBool validate_target_takes_gas_damage(struct Thing* source, struct Thing* target, CrInstance inst_idx, int32_t param1, int32_t param2);
TbBool validate_target_requires_cleansing(struct Thing* source, struct Thing* target, CrInstance inst_idx, int32_t param1, int32_t param2);

TbBool search_target_generic(struct Thing *source, CrInstance inst_idx, ThingIndex **targets, uint16_t *found_count, int32_t param1, int32_t param2);
TbBool search_target_ranged_heal(struct Thing *source, CrInstance inst_idx, ThingIndex **targets, uint16_t *found_count, int32_t param1, int32_t param2);
Expand Down
19 changes: 19 additions & 0 deletions src/creature_states_combt.c
Original file line number Diff line number Diff line change
Expand Up @@ -3494,4 +3494,23 @@ short creature_damage_walls(struct Thing *creatng)

}

TbBool creature_requires_cleansing(const struct Thing* thing, SpellKind spell_idx)
{
struct CreatureControl* cctrl = creature_control_get_from_thing(thing);
struct SpellConfig* spconf = get_spell_config(spell_idx);
for (long i = 0; i < CREATURE_MAX_SPELLS_CASTED_AT; i++)
{
struct CastedSpellData *cspell = &cctrl->casted_spells[i];
struct SpellConfig* spconf2 = get_spell_config(cspell->spkind);
if (flag_is_set(spconf->cleanse_flags, spconf2->spell_flags))
{
if (creature_under_spell_effect(thing, spconf2->spell_flags))
{
return true;
}
}
}
return false;
}

/******************************************************************************/
1 change: 1 addition & 0 deletions src/creature_states_combt.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ TbBool creature_has_creature_in_combat(const struct Thing *thing, const struct T
TbBool set_creature_combat_state(struct Thing *fighter, struct Thing *enemy, CrAttackType attack_type);
TbBool battle_with_creature_of_player(PlayerNumber plyr_idx, BattleIndex battle_id);
TbBool creature_would_benefit_from_healing(const struct Thing* thing);
TbBool creature_requires_cleansing(const struct Thing* thing, SpellKind spell_idx);

void reset_postal_instance_cache();
CrInstance get_postal_instance_to_use(const struct Thing *thing, unsigned long dist);
Expand Down
3 changes: 3 additions & 0 deletions src/creature_states_prisn.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ short creature_arrived_at_prison(struct Thing *creatng)
{
clean_spell_effect(creatng, CSAfF_Invisibility);
}
if (creature_under_spell_effect(creatng, CSAfF_SpellBlocks)) {
clean_spell_effect(creatng, CSAfF_SpellBlocks);
}
if (creatng->light_id != 0) {
light_delete_light(creatng->light_id);
creatng->light_id = 0;
Expand Down
8 changes: 6 additions & 2 deletions src/engine_redraw.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,14 @@ static void draw_creature_view_icons(struct Thing* creatng)
y = MyScreenHeight - scale_ui_value_lofi(spr->SHeight * 2);
}
struct CreatureControl *cctrl = creature_control_get_from_thing(creatng);
struct SpellConfig *spconf;
for (SpellKind spell_idx = 0; spell_idx < CREATURE_MAX_SPELLS_CASTED_AT; spell_idx++)
{
spconf = get_spell_config(cctrl->casted_spells[spell_idx].spkind);
struct CastedSpellData* cspell = &cctrl->casted_spells[spell_idx];
if (cspell->spkind == 0)
{
continue;
}
struct SpellConfig *spconf = get_spell_config(cspell->spkind);
long spridx = spconf->medsym_sprite_idx;
if (flag_is_set(spconf->spell_flags, CSAfF_Invisibility))
{
Expand Down
1 change: 1 addition & 0 deletions src/gui_boxmenu.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ struct GuiBoxOption gui_instance_option_list[] = {
{"Disease",1,NULL, gf_change_creature_instance,CrInst_CAST_SPELL_DISEASE, 0, 0, CrInst_CAST_SPELL_DISEASE, 0, 0, 0, true},
{"Chicken",1,NULL, gf_change_creature_instance,CrInst_CAST_SPELL_CHICKEN, 0, 0, CrInst_CAST_SPELL_CHICKEN, 0, 0, 0, true},
{"Time Bomb",1,NULL, gf_change_creature_instance,CrInst_CAST_SPELL_TIME_BOMB, 0, 0, CrInst_CAST_SPELL_TIME_BOMB, 0, 0, 0, true},
{"Cleanse", 1, NULL, gf_change_creature_instance, CrInst_CLEANSE, 0, 0, CrInst_CLEANSE, 0, 0, 0, true},
{"!", 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, false},
};

Expand Down
4 changes: 4 additions & 0 deletions src/room_jobs.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ TbBool add_creature_to_torture_room(struct Thing *creatng, const struct Room *ro
{
clean_spell_effect(creatng, CSAfF_Invisibility);
}
if (creature_under_spell_effect(creatng, CSAfF_SpellBlocks))
{
clean_spell_effect(creatng, CSAfF_SpellBlocks);
}
if (room->owner != game.neutral_player_num)
{
struct Dungeon* dungeon = get_dungeon(room->owner);
Expand Down
45 changes: 39 additions & 6 deletions src/thing_creature.c
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,17 @@ TbBool creature_is_immune_to_spell_effect_f(const struct Thing *thing, unsigned
{
return false;
}
if (creature_under_spell_effect(thing, CSAfF_SpellBlocks))
{
struct CreatureControl *cctrl = creature_control_get_from_thing(thing);
if (!creature_control_invalid(cctrl))
{
if (flag_is_set(cctrl->cleanse_flags, spell_flags))
{
return true;
}
}
}
return flag_is_set(crconf->immunity_flags, spell_flags);
}

Expand Down Expand Up @@ -1177,6 +1188,14 @@ TbBool set_thing_spell_flags_f(struct Thing *thing, SpellKind spell_idx, GameTur
}
affected = true;
}
// Spell Blocks.
if (flag_is_set(spconf->spell_flags, CSAfF_SpellBlocks)
&& (!creature_is_immune_to_spell_effect(thing, CSAfF_SpellBlocks)))
{
set_flag(cctrl->spell_flags, CSAfF_SpellBlocks);
cctrl->cleanse_flags = spconf->cleanse_flags;
affected = true;
}
if (!affected)
{
SYNCDBG(7, "%s: No spell flags %d to set on %s index %d", func_name, (uint)spconf->spell_flags, thing_model_name(thing), (int)thing->index);
Expand Down Expand Up @@ -1358,6 +1377,14 @@ TbBool clear_thing_spell_flags_f(struct Thing *thing, unsigned long spell_flags,
// 'CSAfF_Heal' is never set but we still want to mark it cleared to free the spell slot.
cleared = true;
}
// Spell Blocks.
if (flag_is_set(spell_flags, CSAfF_SpellBlocks)
&& (creature_under_spell_effect(thing, CSAfF_SpellBlocks)))
{
clear_flag(cctrl->spell_flags, CSAfF_SpellBlocks);
cctrl->cleanse_flags = 0;
cleared = true;
}
if (!cleared)
{
SYNCDBG(7, "%s: No spell flags %d to clear on %s index %d", func_name, (uint)spell_flags, thing_model_name(thing), (int)thing->index);
Expand Down Expand Up @@ -1482,12 +1509,12 @@ void apply_spell_effect_to_thing(struct Thing *thing, SpellKind spell_idx, CrtrE
}
GameTurnDelta duration = get_spell_full_duration(spell_idx, spell_level);
// Check for cleansing one-time effect.
if (spconf->cleanse_flags > 0
&& any_flag_is_set(spconf->cleanse_flags, cctrl->spell_flags))
if ((spconf->cleanse_flags > 0)
&& (any_flag_is_set(spconf->cleanse_flags, cctrl->spell_flags)))
{
clean_spell_effect(thing, spconf->cleanse_flags);
if (spconf->spell_flags == 0
&& !spell_is_continuous(spell_idx, duration))
if ((spconf->spell_flags == 0)
&& (!spell_is_continuous(spell_idx, duration)))
{
update_aura_effect_to_thing(thing, spell_idx);
return; // Exit the function, no continuous effect to apply.
Expand Down Expand Up @@ -1874,7 +1901,7 @@ void process_thing_spell_effects(struct Thing *thing)
struct SpellConfig *spconf = get_spell_config(cspell->spkind);
// Terminate the spell if its duration expires, or if the spell flags are cleared and no other continuous effects are active.
if ((cspell->duration <= 0)
|| ((spconf->spell_flags > 0) && !flag_is_set(cctrl->spell_flags, spconf->spell_flags) && !spell_is_continuous(cspell->spkind, cspell->duration)))
|| ((spconf->spell_flags > 0) && !any_flag_is_set(cctrl->spell_flags, spconf->spell_flags) && !spell_is_continuous(cspell->spkind, cspell->duration)))
{
terminate_thing_spell_effect(thing, cspell->spkind);
continue;
Expand Down Expand Up @@ -6327,7 +6354,13 @@ TngUpdateRet update_creature(struct Thing *thing)
}
} else
{
if ((cctrl->stateblock_flags == 0) || creature_state_cannot_be_blocked(thing))
if (creature_under_spell_effect(thing, CSAfF_Freeze))
{
if (creature_instance_is_available(thing, CrInst_CLEANSE) && creature_instance_has_reset(thing, CrInst_CLEANSE)) {
cctrl->stopped_for_hand_turns = 0;
}
}
else if ((cctrl->stateblock_flags == 0) || creature_state_cannot_be_blocked(thing))
{
if (cctrl->stopped_for_hand_turns > 0)
{
Expand Down