Shakshuka Man's python scripts

From Infinite Worlds
Jump to navigation Jump to search

How to setup[edit]

The following is a single-file python module. To use, save the below as iw.py.

iw.py source code

from __future__ import annotations

import hashlib
import json
import random
import string
import uuid
from dataclasses import asdict, dataclass, field, fields, is_dataclass, MISSING
from enum import StrEnum
from typing import Any, Optional, Union, get_type_hints, get_origin, get_args


# ---- ID Generation ---------------------------------------------------------

_SHORT_ID_CHARS = string.ascii_letters + string.digits
_SHORT_ID_FIRST_CHARS = string.ascii_letters


class IDGenerator:
    """Deterministic ID generator. A single shared instance ensures uniqueness
    across all call sites without requiring manual ID management."""

    def __init__(self, seed: int = 0):
        self._rng = random.Random(seed)

    def next_uuid(self) -> str:
        return str(uuid.UUID(int=self._rng.getrandbits(128), version=4))

    def next_short(self, length: int = 8) -> str:
        first = self._rng.choice(_SHORT_ID_FIRST_CHARS)
        rest = ''.join(self._rng.choice(_SHORT_ID_CHARS) for _ in range(length - 1))
        return first + rest

    def seed(self, value: int) -> None:
        self._rng = random.Random(value)


_id = IDGenerator()


# ---- Enums -----------------------------------------------------------------

class TrackedItemDataType(StrEnum):
    TEXT = "text"
    NUMBER = "number"
    XML = "xml"


class TrackedItemVisibility(StrEnum):
    EVERYONE = "everyone"
    AI_ONLY = "ai_only"
    PLAYER_ONLY = "player_only"
    HIDDEN = "hidden"


class TrackedItemInitialValueSource(StrEnum):
    SAME = "same"
    CHARACTER = "character"
    PLAYER = "player"


class TrackedItemAction(StrEnum):
    SET = "set"
    ADD = "add"
    SUBTRACT = "subtract"
    REPLACE = "replace"


class Inequality(StrEnum):
    IS_EXACTLY = "is_exactly"
    AT_LEAST = "at_least"
    AT_MOST = "at_most"


class EffectType(StrEnum):
    SHOW_MESSAGE = "effectShowMessage"
    TELL_AI = "effectTellAIWhatToDo"
    CHANGE_MAIN_INSTRUCTIONS = "effectChangeMainInstructions"
    MODIFY_INSTRUCTION_BLOCK = "effectModifyInstructionBlock"
    CHANGE_AUTHOR_STYLE = "effectChangeAuthorStyle"
    CHANGE_DESCRIPTION_INSTRUCTIONS = "effectChangeDescriptionInstructions"
    CHANGE_OBJECTIVE = "effectChangeObjective"
    CHANGE_VICTORY_CONDITION = "effectChangeVictoryCondition"
    CHANGE_DEFEAT_CONDITION = "effectChangeDefeatCondition"
    CHANGE_PC_NAME = "effectChangePCName"
    CHANGE_PC_DESCRIPTION = "effectChangePCDescription"
    CHANGE_PC_SKILL = "effectChangePCSkill"
    SET_TRACKED_ITEM_VALUE = "effectSetTrackedItemValue"
    FIRE_RANDOM_TRIGGER = "effectFireRandomTrigger"
    MODIFY_KEYWORD_BLOCK = "effectModifyKeywordBlock"
    ENDS_GAME = "effectEndsGame"
    MODIFY_TRACKED_ITEM_DETAILS = "effectModifyTrackedItemDetails"


class ConditionType(StrEnum):
    ON_TURN = "triggerOnTurn"
    ON_EVENT = "triggerOnEvent"
    ON_CHARACTER = "triggerOnCharacter"
    ON_TRACKED_ITEM = "triggerOnTrackedItem"
    ON_RANDOM_CHANCE = "triggerOnRandomChance"
    PREREQS = "triggerPrereqs"


class LogicOperator(StrEnum):
    AND = "and"
    OR = "or"


# ---- Dataclasses -----------------------------------------------------------

@dataclass
class ImagePromptDetails:
    illustrClothes: str = ""
    illustrSetting: str = ""
    illustrSubject: str = ""
    illustrAppearance: str = ""
    illustrIsCharacter: bool = True
    illustrExpressionPosition: str = ""


@dataclass
class PermissionsOnceShared:
    sharing: bool = True
    editing: bool = True


@dataclass
class PortraitPromptDetails:
    illustrClothes: str = ""
    illustrSetting: str = ""
    illustrAppearance: str = ""
    illustrExpressionPosition: str = ""


@dataclass
class VictoryDefeatCondition:
    condition: str
    text: str
    alreadyFired: bool = False


@dataclass
class InitialTrackedItemValue:
    id: str
    name: str
    initialPCValue: str = ""
    visibility: TrackedItemVisibility = TrackedItemVisibility.AI_ONLY
    initialValueBasedOnPC: TrackedItemInitialValueSource = TrackedItemInitialValueSource.CHARACTER


@dataclass
class TrackedItem:
    name: str
    id: str = field(default_factory=_id.next_short)
    dataType: TrackedItemDataType = TrackedItemDataType.TEXT
    visibility: TrackedItemVisibility = TrackedItemVisibility.EVERYONE
    description: str = ""
    updateInstructions: str = ""
    initialValue: str = ""
    initialValueBasedOnPC: TrackedItemInitialValueSource = TrackedItemInitialValueSource.SAME
    autoUpdate: bool = False


@dataclass
class NPC:
    name: str
    id: str = field(default_factory=_id.next_short)
    detail: str = ""
    one_liner: str = ""
    appearance: str = ""
    location: str = ""
    secret_info: str = ""
    names: list[str] = field(default_factory=list)
    img_appearance: str = ""
    img_clothing: str = ""


@dataclass
class InstructionBlock:
    name: str
    content: str
    id: str = field(default_factory=_id.next_short)
    selectedAIProfiles: Optional[list[str]] = None


@dataclass
class LoreBookEntry:
    name: str
    content: str
    id: str = field(default_factory=_id.next_short)
    keywords: list[str] = field(default_factory=list)


@dataclass
class PossibleCharacter:
    name: str
    description: str = ""
    portrait: str = ""
    portraitPromptDetails: PortraitPromptDetails = field(default_factory=PortraitPromptDetails)
    fullSizePortrait: str = ""
    portraitOptions: list[str] = field(default_factory=list)
    fullSizePortraitOptions: list[str] = field(default_factory=list)
    currentPortraitIndex: int = 0
    characterId: str = field(default_factory=_id.next_short)
    skills: dict[str, int] = field(default_factory=dict)
    initialTrackedItemValues: list[InitialTrackedItemValue] = field(default_factory=list)


@dataclass
class TriggerEffect:
    type: EffectType
    data: Any
    id: str = field(default_factory=_id.next_uuid)
    trackedItemID: Optional[str] = None


@dataclass
class TriggerCondition:
    data: Any
    id: str = field(default_factory=_id.next_uuid)
    type: Optional[ConditionType] = None
    category: Optional[str] = None
    trackedItemID: Optional[str] = None
    inequality: Optional[Inequality] = None
    operator: Optional[LogicOperator] = None


@dataclass
class TriggerEvent:
    name: str
    id: str = field(default_factory=_id.next_short)
    triggerEffects: list[TriggerEffect] = field(default_factory=list)
    triggerConditions: list[TriggerCondition] = field(default_factory=list)
    advancedLogic: Optional[bool] = None
    triggerOnStartOfGame: Optional[bool] = None
    canTriggerMoreThanOnce: Optional[bool] = None


# ---- World -----------------------------------------------------------------

_OPTIONAL_FIELDS: set[str] = {
    "descriptionRequest", "summaryRequest", "charSelectText",
    "instructionBlocks", "loreBookEntries", "trackedItems", "NPCs",
    "version", "autoAdvanceVersion", "designNotes",
}


def _convert(hint, val):
    """Convert a JSON value to the appropriate Python type based on a type hint."""
    if val is None:
        return None
    origin = get_origin(hint)
    args = get_args(hint)
    if origin is Union:
        non_none = [a for a in args if a is not type(None)]
        if len(non_none) == 1:
            return _convert(non_none[0], val)
        return val
    if origin is list and args:
        return [_convert(args[0], item) for item in val]
    if origin is dict:
        return val
    if is_dataclass(hint):
        return _build(hint, val)
    if isinstance(hint, type) and issubclass(hint, StrEnum):
        return hint(val)
    return val


_IGNORED_FIELDS = {"positionInList"}


def _build(cls, data: dict):
    """Construct a dataclass instance from a dict, recursively converting nested types."""
    hints = get_type_hints(cls)
    known = {f.name for f in fields(cls)} | _IGNORED_FIELDS
    unknown = set(data) - known
    if unknown:
        raise NotImplementedError(
            f"{cls.__name__} has no fields for: {', '.join(sorted(unknown))}"
        )
    kwargs = {}
    for f in fields(cls):
        if f.name not in data:
            continue
        kwargs[f.name] = _convert(hints[f.name], data[f.name])
    return cls(**kwargs)


def _strip_none(obj):
    """Recursively remove None values from dicts (and nested structures)."""
    if isinstance(obj, dict):
        return {k: _strip_none(v) for k, v in obj.items() if v is not None}
    if isinstance(obj, list):
        return [_strip_none(x) for x in obj]
    return obj


@dataclass
class World:
    favorite: bool = False
    title: str = ""
    description: str = ""
    background: str = ""
    instructions: str = ""
    authorStyle: str = ""
    recommendedAIModel: Optional[str] = None
    firstInput: str = ""
    objective: str = ""

    imageModel: str = "manticore"
    imageStyle: str = "photo_1"
    illustrationStyleNonCharacterLowPriority: str = ""
    illustrationStyleNonCharacterHighPriority: str = ""
    illustrationStyleCharacterLowPriority: str = ""
    illustrationStyleCharacterHighPriority: str = ""
    imageStyleCharacterPre: str = ""
    imageStyleCharacterPost: str = ""
    imageStyleNonCharacterPre: str = ""
    imageStyleNonCharacterPost: str = ""

    nsfw: bool = False
    contentWarnings: str = ""
    enableAISpecificInstructionBlocks: bool = False

    previewImage: str = ""
    fullSizePreviewImage: str = ""
    previewImageOptions: list[str] = field(default_factory=list)
    fullSizePreviewImageOptions: list[str] = field(default_factory=list)
    currentPreviewImageIndex: int = -1

    imagePromptDetails: ImagePromptDetails = field(default_factory=ImagePromptDetails)
    permissionsOnceShared: PermissionsOnceShared = field(default_factory=PermissionsOnceShared)

    allowChangeCharacterName: bool = True
    allowChangeCharacterDescription: bool = True
    allowChangeCharacterSkills: bool = False
    allowChangeCharacterItemValues: bool = False
    allowChangeCharacterPortrait: bool = True
    allowChangeCharacterNewPortrait: bool = True

    schemaVersion: Optional[int] = 2
    skills: list[str] = field(default_factory=list)
    possibleCharacters: list[PossibleCharacter] = field(default_factory=list)
    triggerEvents: list[TriggerEvent] = field(default_factory=list)
    victoryCondition: Optional[VictoryDefeatCondition] = None
    defeatCondition: Optional[VictoryDefeatCondition] = None

    # Optional fields — only included in JSON when non-default
    descriptionRequest: str = ""
    summaryRequest: str = ""
    charSelectText: Optional[str] = None
    instructionBlocks: list[InstructionBlock] = field(default_factory=list)
    loreBookEntries: list[LoreBookEntry] = field(default_factory=list)
    trackedItems: list[TrackedItem] = field(default_factory=list)
    NPCs: list[NPC] = field(default_factory=list)
    version: str = "0.01"
    autoAdvanceVersion: bool = True
    designNotes: Optional[str] = None

    def to_dict(self) -> dict:
        raw = asdict(self)
        result = {}
        for f in fields(self):
            val = raw[f.name]
            if f.name in _OPTIONAL_FIELDS:
                if f.default is not MISSING and val == f.default:
                    continue
                if f.default_factory is not MISSING and val == f.default_factory():
                    continue
            if isinstance(val, list):
                val = [_strip_none(item) for item in val]
            result[f.name] = val
        for key in ("trackedItems", "NPCs"):
            for i, item in enumerate(result.get(key, [])):
                item["positionInList"] = i
        return result

    def to_json(self) -> str:
        return json.dumps(self.to_dict(), indent=4)

    @classmethod
    def from_dict(cls, data: dict) -> World:
        # Re-seed the ID generator with the hash of the data to avoid ID collisions
        _id.seed(int(hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest(), 16))
        return _build(cls, data)

    @classmethod
    def from_json(cls, json_str: str) -> World:
        return cls.from_dict(json.loads(json_str))

How to use[edit]

Creating and loading worlds[edit]

import iw

# Create a minimal world
w = iw.World(title="My World")
print(w.to_json())

# Load an existing world
w = iw.World.from_json(open("my_world.json").read())

Updating an existing world[edit]

import iw

# Load
w = iw.World.from_json(open("my_world.json").read())

# Tweak
w.trackedItems.append(iw.TrackedItem(name="Sanity", initialValue="100"))
w.title = "Updated World"

# Export
with open("my_world.json", "w") as f:
    f.write(w.to_json())

Inserting items mid-list[edit]

The display order of tracked items and NPCs is determined by their position in the Python list — positionInList is assigned automatically on export. Use standard list operations to reorder:

import iw

w = iw.World.from_json(open("my_world.json").read())

# Insert a new tracked item at position 1 (between the first and second items)
w.trackedItems.insert(1, iw.TrackedItem(name="Morale", initialValue="High"))

# The exported JSON will have positionInList 0, 1, 2, ... assigned automatically
with open("my_world.json", "w") as f:
    f.write(w.to_json())

Programmatically generating world elements[edit]

Generate triggers from data. This example creates health-threshold triggers — at 75, 50, 25, and 0 HP:

import iw

health = iw.TrackedItem(
    name="Health",
    dataType=iw.TrackedItemDataType.NUMBER,
    visibility=iw.TrackedItemVisibility.EVERYONE,
    initialValue="100",
)

thresholds = [
    (75, "You're bruised and battered."),
    (50, "You're badly wounded. Find help soon."),
    (25, "You can barely stand. Death is near."),
    (0,  "You collapse. The world fades to black."),
]

triggers = []
for hp, message in thresholds:
    triggers.append(iw.TriggerEvent(
        name=f"Health drops to {hp}",
        triggerConditions=[
            iw.TriggerCondition(
                data={
                    "inequality": iw.Inequality.AT_MOST,
                    "requiredValue": str(hp),
                    "trackedItemID": health.id,
                },
                type=iw.ConditionType.ON_TRACKED_ITEM,
                category="condition",
                inequality=iw.Inequality.AT_MOST,
                trackedItemID=health.id,
            ),
        ],
        triggerEffects=[
            iw.TriggerEffect(type=iw.EffectType.SHOW_MESSAGE, data=message),
        ],
    ))

# The final threshold also ends the game
triggers[-1].triggerEffects.append(
    iw.TriggerEffect(type=iw.EffectType.ENDS_GAME, data=True)
)

w = iw.World(
    title="Health System",
    trackedItems=[health],
    triggerEvents=triggers,
)

Dataclass reference[edit]

World[edit]

The top-level container. All fields have defaults — iw.World() is valid.

Field Type Default Description
title str "" World title
description str "" Description shown before starting world
background str "" Background shown to the player at the start of the first turn
instructions str "" Main instructions for the AI
authorStyle str "" Writing style guidance
firstInput str "" What should happen on the first turn
objective str "" The player's objective
skills list[str] [] List of skills. If empty, when the JSON is imported a generic skill will be created.
possibleCharacters list[PossibleCharacter] [] Playable characters
triggerEvents list[TriggerEvent] [] Trigger events
trackedItems list[TrackedItem] [] Tracked items
instructionBlocks list[InstructionBlock] [] Extra instruction blocks
loreBookEntries list[LoreBookEntry] [] Keyword instruction blocks (internally called lorebooks)
NPCs list[NPC] [] Other characters (internally called NPCs)
victoryCondition VictoryDefeatCondition None Victory condition
defeatCondition VictoryDefeatCondition None Defeat condition
charSelectText str None Text shown on character selection screen
descriptionRequest str "" Custom description instructions
summaryRequest str "" Custom summarization instructions
designNotes str None Design notes

InstructionBlock[edit]

An extra block of instructions for the AI.

Field Type Default Description
name str required Block name
content str required Block content
id str auto Unique ID
selectedAIProfiles list[str] None If set, only active for these AI models

PossibleCharacter[edit]

A playable character the player can choose.

Field Type Default Description
name str required Character name
description str "" Character description
portrait str "" Portrait image URL
portraitPromptDetails PortraitPromptDetails all empty Image generation hints
fullSizePortrait str "" Full-size portrait URL
portraitOptions list[str] [] Alternative portrait URLs
fullSizePortraitOptions list[str] [] Alternative full-size portrait URLs
currentPortraitIndex int 0 Selected portrait index
characterId str auto Unique character ID
skills dict[str, int] {} Skill name to value mapping
initialTrackedItemValues list[InitialTrackedItemValue] [] Per-character starting values for tracked items

TrackedItem[edit]

A value tracked throughout the game (stats, inventory, flags, etc.).

Field Type Default Description
name str required Display name
id str auto Unique ID
dataType TrackedItemDataType TEXT Data type (TEXT, NUMBER, XML)
visibility TrackedItemVisibility EVERYONE Who can see it (EVERYONE, AI_ONLY, PLAYER_ONLY, HIDDEN)
description str "" Description of the item
updateInstructions str "" Instructions for the AI on how to update
initialValue str "" Starting value
initialValueBasedOnPC TrackedItemInitialValueSource SAME Whether value varies by character (SAME, CHARACTER, PLAYER)
autoUpdate bool False Whether the AI auto-updates this item

InitialTrackedItemValue[edit]

A per-character starting value for a tracked item. Used inside PossibleCharacter.initialTrackedItemValues.

Field Type Default Description
id str required ID of the TrackedItem this overrides
name str required Name of the TrackedItem
initialPCValue str "" Starting value for this character
visibility TrackedItemVisibility AI_ONLY Visibility override
initialValueBasedOnPC TrackedItemInitialValueSource CHARACTER Source mode

NPC[edit]

Other characters, internally called NPCs.

Field Type Default Description
name str required Character name
id str auto Unique ID
detail str "" Detailed backstory / personality
one_liner str "" Brief summary
appearance str "" Physical appearance description
location str "" Where they can be found
secret_info str "" AI-only secret information
names list[str] [] Name variations / aliases
img_appearance str "" Image generation appearance prompt
img_clothing str "" Image generation clothing prompt

LoreBookEntry[edit]

A keyword-activated lore entry injected when keywords appear in context.

Field Type Default Description
name str required Entry name
content str required Entry content
id str auto Unique ID
keywords list[str] [] Triggering keywords

VictoryDefeatCondition[edit]

A win or loss condition.

Field Type Default Description
condition str required Condition description for the AI
text str required Text shown to the player
alreadyFired bool False Whether this has already triggered

TriggerEvent[edit]

An event that fires when conditions are met, applying effects.

Field Type Default Description
name str required Trigger name
id str auto Unique ID
triggerEffects list[TriggerEffect] [] Effects to apply
triggerConditions list[TriggerCondition] [] Conditions to check
advancedLogic bool None Enable nested AND/OR logic
triggerOnStartOfGame bool None Fire at game start
canTriggerMoreThanOnce bool None Allow repeat firing

TriggerEffect[edit]

A single effect inside a trigger.

Field Type Default Description
type EffectType required Effect type
data Any required Effect payload (str, dict, list, or bool depending on type)
id str auto (uuid) Unique ID
trackedItemID str None Related tracked item, if applicable

TriggerCondition[edit]

A single condition inside a trigger.

Field Type Default Description
data Any required Condition payload (int, str, list, or dict depending on type)
id str auto (uuid) Unique ID
type ConditionType None Condition type (omit for logic groups)
category str None "condition" or "logic"
trackedItemID str None Related tracked item
inequality Inequality None Comparison operator for tracked item conditions
operator LogicOperator None AND / OR for logic groups

Enums reference[edit]

Enum Values
TrackedItemDataType TEXT, NUMBER, XML
TrackedItemVisibility EVERYONE, AI_ONLY, PLAYER_ONLY, HIDDEN
TrackedItemInitialValueSource SAME, CHARACTER, PLAYER
TrackedItemAction SET, ADD, SUBTRACT, REPLACE
Inequality IS_EXACTLY, AT_LEAST, AT_MOST
EffectType SHOW_MESSAGE, TELL_AI, CHANGE_MAIN_INSTRUCTIONS, MODIFY_INSTRUCTION_BLOCK, CHANGE_AUTHOR_STYLE, CHANGE_DESCRIPTION_INSTRUCTIONS, CHANGE_OBJECTIVE, CHANGE_VICTORY_CONDITION, CHANGE_DEFEAT_CONDITION, CHANGE_PC_NAME, CHANGE_PC_DESCRIPTION, CHANGE_PC_SKILL, SET_TRACKED_ITEM_VALUE, FIRE_RANDOM_TRIGGER, MODIFY_KEYWORD_BLOCK, ENDS_GAME, MODIFY_TRACKED_ITEM_DETAILS
ConditionType ON_TURN, ON_EVENT, ON_CHARACTER, ON_TRACKED_ITEM, ON_RANDOM_CHANCE, PREREQS
LogicOperator AND, OR