Conditions - SuIT-pub/Mind-the-School GitHub Wiki
Audience: Developers comfortable with Python/Ren'Py who need to gate content in Mind the School — events, situations, unlockables, effects, votes. This guide explains what a Condition is, the base contract, how to combine conditions, the shared
Optionsystem, and the full catalog of condition types.Scope: Conditions only. They are the shared gating primitive used by nearly every other system (events, situations, unlockables, selectors, PTA). Those systems have their own guides; this one is the reference for the conditions themselves.
A Condition answers one yes/no question about the game state. You build one and hand it to whatever system does the gating (an event, a situation threshold, an unlockable's visibility, …):
# "school level ≥ 2 AND at least $1500 AND the intro event was seen"
AND(
LevelCondition("2"),
MoneyCondition(1500),
EventSeenCondition(True, "intro_done"),
)- A bare condition is checked with
condition.is_fulfilled(**kwargs). - A group is checked with
ConditionStorage(*conditions).is_fulfilled(**kwargs)(all must pass) — most systems wrap your conditions in a storage for you. - Combine with
AND/OR/NOR/NOT/XOR. - While work is in progress, use
PlaceholderCondition()(never fulfilled).
- What is a Condition?
- The base contract
- ConditionStorage
- Logic combinators
- Options
- The condition catalog
- Common patterns
- Conventions
- Troubleshooting
- Reference tables
A Condition is a small object that evaluates the game state and returns
True/False. It is the universal gating primitive: events use conditions to
decide when they can fire, situation thresholds use them as gate conditions, teasers
use them as unlock triggers, unlockables use them for visibility, PTA votes use them
for cost gates, and so on.
Conditions are declarative and reusable. You never subclass to gate ordinary content — you compose the existing types. Because everything is coupled only through keys and conditions, a mod can gate on base content and vice versa.
Every condition subclasses Condition(*options):
-
check_condition(**kwargs) -> bool(abstract) — the actual test. Subclasses implement this. -
is_fulfilled(**kwargs) -> bool— the public entry point. It wrapscheck_conditionwith universal short-circuits: it returnsTruein replay mode / journal gallery (in_replay,check_in_replay,in_journal_gallery), andTrueif the condition carries theOptionaloption. Otherwise it delegates tocheck_condition. Callis_fulfilled, notcheck_condition. -
get_name() -> str(abstract) — a human-readable identifier, used in logs and default description text. -
type(property, on many subclasses) — a string tag ("stat","money","timer", …) used byfind_by_typeto pull specific conditions back out of a storage.
**kwargs carry the evaluation context — most importantly char_obj (the character
or school the condition applies to; defaults to the school), plus event/selector
data. Conditions read what they need and ignore the rest.
Display hooks (mostly for the journal): display_in_list / display_in_desc flags
(default False; set True by display-worthy subclasses like StatCondition),
to_list_text / to_desc_text, and get_diff / calculate_probability (how close
to fulfilled — feeds vote probability and sorting).
Note: "Blocking" is not a Condition concept. The old blocking attribute that once controlled journal display was removed — a condition's own visibility is driven by
display_in_list/display_in_desc(above). Situation blocking thresholds are an unrelated, threshold-level mechanic and live in the Situations guide, not here.
ConditionStorage(*conditions) is the container systems use to hold a set of
conditions. Key behavior:
-
is_fulfilled(**kwargs)— AND semantics:Trueonly if every member is fulfilled. (An empty storage isTrue.) -
add_conditions(*conditions)/add_condition(c)/add_storage(other)— build it up incrementally; duplicates (same object) are skipped. -
find_by_type(type)— returns all members whosetypematches (recursing into combinators). This is how systems extract, e.g., theTimerConditionorMoneyConditionout of a mixed list. - Sorts members into
list_conditions/desc_conditionsfor the journal, and tracks whether aLockConditionis present (is_locked).
You rarely construct a storage yourself for gating — you pass a list of conditions
to a system and it wraps them. You do use ConditionStorage when writing a system
that gates on author-supplied conditions.
Conditions compose. Each combinator is itself a Condition, so it nests anywhere a
condition is accepted:
| Combinator | Fulfilled when |
|---|---|
AND(*conditions) |
all are fulfilled |
OR(*conditions) |
at least one is fulfilled |
NOR(*conditions) |
none are fulfilled |
NOT(condition) |
the single inner condition is not fulfilled |
XOR(*conditions) |
an odd number are fulfilled (exclusive) |
OR(
AND(LevelCondition("3"), EventSeenCondition(True, "sb_event_3")),
MoneyCondition(5000),
)Note: a bare ConditionStorage / a plain condition list is already an implicit
AND. Use the explicit AND(...) only when you need it inside an OR/NOT/etc.
Both conditions and effects accept trailing *options (from option.rpy). An
Option tweaks how the host is evaluated or displayed — see the
Options guide for the full shared catalog. The
condition-relevant ones:
| Option | Effect on a condition |
|---|---|
OptionalOption() ("Optional") |
is_fulfilled always returns True — a soft, non-gating marker |
ShowBlockedOption() |
let the host show even while blocked (host-interpreted) |
NoHighlightOption() / ForceHighlightOption()
|
control map/journal highlighting of the host |
PriorityOption(priority) |
tag the host with a priority bucket (events) |
NoOverrideOption() |
opt out of override/latch key rewriting (e.g. resolution latches) |
Other options are event- or effect-specific (FragmentRepeatOption,
EffectNoRevertOption, MoneyEscrowOption, …) — see the
Options guide. Options are passed positionally after
the constructor's own args: StatCondition(OptionalOption(), corruption=20).
All constructors below are Condition subclasses. *options is always accepted
last. Numeric arguments written as "2", "2+", "3-7", "1,3,5" are value
ranges — the shared number-pattern grammar is documented in
Value Ranges.
| Constructor | Checks |
|---|---|
StatCondition(*options, char_obj=None, **stats) |
each stat=threshold kwarg is met on char_obj (default school), e.g. StatCondition(corruption=20, happiness=50)
|
StatLimitCondition(stat, char_obj=None, *options) |
the stat is at its current level cap |
LevelCondition(value, *options, char_obj=None) |
character/school level meets value (e.g. "2", "2+"). Default char is school. Named stages (dress/talk/behaviour): School Levels
|
ProficiencyCondition(proficiency, *options, xp=-1, level=-1) |
a proficiency's xp/level threshold |
MaxLevelEventCondition(value, *options) |
max-level event progress |
| Constructor | Checks |
|---|---|
MoneyCondition(value, *options) |
available money is ≥ value (a plain number, always a minimum — does not parse value ranges; pass an int, not "1500+") |
ItemCondition(item_key, amount=1, *options) |
at least amount of an inventory item (Items & Inventory) |
DeliveryCondition(*options) |
a delivery is available |
| Constructor | Checks |
|---|---|
GameDataCondition(key, value, *options) |
a GameData entry equals value
|
BoolCondition(value, *options) |
a constant True/False (useful as a fixed gate) |
ValueCondition(key, value, *options) |
a registered value equals value
|
NumValueCondition(key, value, *options) |
a numeric value meets value (accepts a Selector) |
NumCompareCondition(key, value, operation, *options) |
numeric compare (operation ∈ >, <, >=, …) |
CompareCondition(key, value, *options) |
generic equality compare (accepts a Selector) |
KeyCompareCondition(key_1, key_2, operation, *options) |
compare two keys against each other |
ProgressCondition(key, value="", *options) |
an event-series progress step |
| Constructor | Checks |
|---|---|
TimeCondition(*options, **when) |
calendar match via day / week / month / year / daytime / weekday / date; each defaults to "x" (any); condition = "+"/"-"/"" for ≥ / ≤ / = |
TimerCondition(id, *options, **span) |
a named countdown timer (set elsewhere) has elapsed by day= / daytime= / … — also the standard duration / cooldown / grace primitive for situations |
DaytimeChangedCondition(*options) |
the daytime segment just changed |
| Constructor | Checks |
|---|---|
ManualCounterCondition(counter_key, max=1, *options) |
a manually-incremented counter is below max (hard quota; used by measures) |
CounterCondition(counter_key, condition, max=1, *options) |
counts edges of condition up to max
|
LatchCounterCondition(counter_key, max=1, *options) |
a latch that fires after max falling edges (grace counts) |
ManualCondition(is_fulfilled, *options) |
a fixed boolean you set at construction |
| Constructor | Checks |
|---|---|
EventSeenCondition(seen=False, event_name="", *options) |
whether event_name has been seen matches seen
|
FlagCondition(flag_key, exclusive=True, *options) |
the global event flag (current_flag) matches flag_key. exclusive=False also passes while no flag is set; "x" passes under any flag. Auto-added to every event as FlagCondition(None) (Events §5) |
IntroCondition(is_intro=True, *options) |
legacy, unused. Date-based intro gate (before 10 Jan 2023), replaced by FlagCondition("intro")
|
TutorialCondition(*options) |
tutorial state |
CheckReplay(condition, *options) |
wraps a condition so it also evaluates in replay |
RandomCondition(threshold, limit=100, *options) |
a random roll below threshold out of limit (probabilistic gate) |
| Constructor | Checks |
|---|---|
SituationPoolCondition(situation_key, pool_key, *options) |
the situation is active and the named pool's bar is in range |
ThresholdReachedCondition(situation_key, threshold_key, *options) |
a situation threshold has been reached |
UnlockableCondition(unlockable_key, group_index=-1, *options) |
an unlockable (optionally a group level) is unlocked |
BuildingCondition(key, *options) |
a map building is currently open (Maps) |
HasAnythingInCollectionGameDataCondition(collection_key, *options) |
a GameData collection is non-empty (backs building open/closed) |
VoteProposalFreeCondition(*options) |
no PTA proposal is currently scheduled (gate for Schedule Vote) |
JournalVoteCondition(journal_obj, *options) / JournalNRVoteCondition(*options)
|
PTA vote journal state |
| Constructor | Purpose |
|---|---|
PlaceholderCondition(*options) |
never fulfilled — WIP marker until a real condition exists |
LockCondition(*options) |
marks the host as hard-locked (ConditionStorage.is_locked) |
Deprecated (as of 0.2.3 — replaced by the Unlockables system, kept only for save compatibility):
RuleCondition(value, …),ClubCondition(value, …),BuildingLevelCondition(name, level, …)andPTAOverride(char, accept, …). The rule/club conditions are effectively always-false stubs. UseUnlockableConditioninstead (andBuildingConditionfor building state).
Gate on multiple stats: one StatCondition takes several stat kwargs (implicit
AND across them):
StatCondition(corruption=30, inhibition=20)Probabilistic appearance: RandomCondition(35) ≈ a 35 % chance gate — often
combined with hard requirements via AND.
Duration / cooldown / grace (situations): TimerCondition("my_id", daytime=3).
The situation/measure machinery starts and reads the timer; you just declare it.
"Not yet done": NOT(EventSeenCondition(True, "some_event")), or simply
EventSeenCondition(False, "some_event").
Mixed AND/OR: compose explicitly:
AND(LevelCondition("3"), OR(MoneyCondition(2000), UnlockableCondition("grant")))WIP gate: PlaceholderCondition() — the host stays uncompletable until you
replace it. (Some hosts self-test that they carry ≥ 1 condition — e.g. situation
gate thresholds and teasers; a placeholder satisfies "has a condition" but never
fires, so the gate can never pass until you replace it.)
-
Compose, don't subclass. Gate ordinary content with the existing types +
combinators. Only write a new
Conditionsubclass for a genuinely new kind of check (then implementcheck_condition+get_name, and add atypeif it should befind_by_type-discoverable). -
Call
is_fulfilled, nevercheck_condition. Onlyis_fulfilledhonors replay/gallery/Optional. -
Keys are identity. Condition arguments reference keys (
event_name,situation_key, GameDatakey, unlockable key). Use the real, stable keys; a typo silently makes the gate unsatisfiable. -
Prefer
UnlockableCondition/BuildingConditionover the legacyRuleCondition/ClubCondition. -
Placeholders are temporary. Replace every
PlaceholderCondition()before shipping — grep for them.
| Symptom | Likely cause | Fix |
|---|---|---|
| Gate never passes | Wrong/typo'd key, or a PlaceholderCondition() still in place |
Verify the key exists; replace placeholders. |
| Gate passes when it shouldn't | You're in replay/gallery (is_fulfilled returns True there), or an Optional option is attached |
Expected in replay; drop Optional if you meant a hard gate. |
| Stat gate ignored for a character |
char_obj mismatch — a condition bound to one character returns True for others |
Pass the intended char_obj, or leave it unbound to use the context/school. |
find_by_type returns nothing |
The condition type has no type tag, or the tag string differs |
Check the subclass's type property; combinators recurse, plain lists don't. |
| Timer/counter never elapses | Nobody sets/increments it — TimerCondition/ManualCounterCondition only read state |
Ensure the owning system (measure, situation) starts the timer / increments the counter. |
Condition(*options) · abstract check_condition + get_name ·
public is_fulfilled · type → find_by_type ·
display_in_list / display_in_desc + to_list_text / to_desc_text.
is_fulfilled = AND · add_conditions /
add_condition / add_storage · find_by_type · get_conditions.
AND · OR · NOR · NOT(single) · XOR (odd count).
> · < · >= · <= · == — a separate argument on NumCompareCondition /
KeyCompareCondition. Not to be confused with the value-range suffix form ("20+",
"3-7") that stat/level/time checks parse — that grammar is documented in
Value Ranges.
day · week · month · year · daytime · weekday · date · condition
(+/-/``) for TimeCondition; span keys `day=` / `daytime=` / … for
`TimerCondition`.
-
game/scripts/conditions.rpy— all condition classes +ConditionStorage -
game/scripts/option.rpy— theOption/OptionSetsystem -
game/scripts/selector.rpy—Selectors (some conditions accept one asvalue) -
game/scripts/effects.rpy— the counterpart:Effects (what happens when a gate passes)