diff --git a/MODDING.md b/MODDING.md index 9ca4191..bd5007c 100644 --- a/MODDING.md +++ b/MODDING.md @@ -14,13 +14,13 @@ If you'd like a quick primer on how to use the editor, check either of [these](h The game plays spritesheets, regions, layers, nulls, layer groups, events, trigger sounds, and the position, crop, pivot, scale, rotation, tint, color offset, visibility and easing of keyframes. It doesn't play skew, depth, blur, custom shaders or animation layers. Animation groups (the editor's folders) are fine; the game just looks animations up by name, so keep those unique. -Stage-specific character animations are controlled by `animationSuffix` on each stage in `character.json`. The base stage uses no suffix. Later stages append that stage's suffix. +Stage-specific character animations end with the stage's name. The first stage uses no ending. Example: - stage 1: `Look` -- stage 2 with `"animationSuffix": "Stage2"`: `LookStage2` -- stage 5 with `"animationSuffix": "Stage5"`: `LookStage5` +- stage 2: `LookStage2` +- stage 5: `LookStage5` Animation names in the JSON files should still use just the base name like `Look`, `Neutral`, or `Eat`. The game appends the current stage's suffix automatically. @@ -30,7 +30,7 @@ Know that despite Anm2Ed supporting sounds for animations, don't expect these to There's two folders inside resources; "characters" and "font". -"font" is just a font containing the font used in the selection screen. That's literally it. +"font" holds the one font the whole game uses (Mali, under the SIL Open Font License; see its LICENSE.txt). "common" holds files characters share (sounds, particles, the cursor and so on). Whenever a character names a file, the game looks in the character's folder first, then in "common", so a character can use anything there as is, or replace it by having its own file at the same path. This includes the taskbar's icons, `gfx/ui/icons.anm2`: one region per icon, named after its button (`talk`, `mood`, `spawn`, `autofeed`, `home`, `stats`, `catalog`, `settings`, `cheats`, `help`, plus whatever `icon`s the interaction types name). A character with its own `gfx/ui/icons.anm2` gets its own icons. @@ -50,20 +50,34 @@ Wherever sounds are listed, one is picked at random each time. A list can also b Times are in seconds and chances in percent. The game warns (in its log) about keys it doesn't know, which are usually typos, and files it can't find. -If you're making your own character, your best bet would probably be just to copy the Snivy character and edit it based on your needs, just as a helpful start. +If you're making your own character, your best bet would probably be just to copy the Mini character and edit it based on your needs, just as a helpful start. + +## Fixed files and names +Some things aren't set in the data, they're always found at the same place or by the same name: + +| What | Where | +|---|---| +| The character's anm2 | `gfx/character/character.anm2` | +| Select screen render and portrait | `gfx/preview/render.png` and `gfx/preview/portrait.png` (both optional) | +| The background | `gfx/areas/area.anm2`, with the world's bounds in its `Bounds` null and where items spawn in its `ItemSpawn` null | +| The cursor | `gfx/cursor/cursor.anm2`, with the animations `Default`, `Click`, `Hover` (over an item), `Grab` (an item), `Pan` (with middle mouse), `Zoom` (with the mouse wheel) and `Dispose` (right clicking an item) | +| Stage names | Stages are named `Stage1`, `Stage2` and so on. Past the first, the stage's name is added to every animation's name, and each stage's "How are you feeling?" dialogue is the pool of its name. | +| Dialogue when over capacity | An interaction plays its pool with `Full` added (e.g. `BellyRubFull`) instead, if there is one. | # character.json -This is the main character file where much of the functionality is stored. +This is the main character file where much of the functionality is stored. It's read over `common/character.json`, which holds the defaults (the body's numbers, sounds, particles, the interaction types and so on), so a character only writes what it changes. Objects merge key by key (e.g. `"blink": {"destination": "Head"}` keeps the default `source`, and `"interactTypes": {"Smack": {"scaleAmplitude": 5}}` keeps the rest of Smack), while lists are replaced whole. ```json { - "name": "Snivy", - "anm2": "gfx/character/snivy.anm2", - "weight": 8.1, - "stages": [{"weight": 10, "dialoguePool": "Stage2", "animationSuffix": "Stage2"}], - "animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"}, - "sounds": {"digest": ["sfx/digest1.ogg"], "gurgle": ["sfx/gurgle1.ogg", "sfx/gurgle2.ogg"]} + "name": "Princess Mini", + "color": [0.188, 0.651, 0.424], + "weight": 40, + "weightMax": 100, + "capacity": 1250, + "stages": [45, 60, 70, 90], + "expandAreas": [{"layer": "Torso", "null": "Belly", "scaleMax": 2.5}], + "interactAreas": [{"type": "Rub", "null": "Head", "dialoguePool": "HeadRub"}] } ``` @@ -73,14 +87,11 @@ This is the main character file where much of the functionality is stored. | `name` | string | Name of the character; will appear in dialogue, stats, etc. Defaults to the folder name. | | `description` | string | A general description of the character; will show in the Select screen. | | `credits` | string | Credits text shown in the Select screen's Credits tab. | -| `render` | path | Texture for the character's "render" (i.e., a typical full-body display), will show in the Select screen. | -| `portrait` | path | Texture for the character's "portrait" (i.e., a cropped profile view), will show in the Select screen. | -| `anm2` | path | The character's anm2 file; should have all the character's animations. | | `color` | [r, g, b], 0-1 | UI accent color for the character. | | `weight` | float, kg | The character's starting weight. | | `weightMax` | float, kg | Maximum weight the character can reach. | -| `capacity` | float, kcal | The character's starting capacity. | -| `capacityMin`, `capacityMax` | float, kcal | The range capacity stays within. | +| `capacity` | float, kcal | The character's starting capacity, and the least it can go down to. | +| `capacityMax` | float, kcal | The most capacity can grow to. | | `overstuffedMultiplier` | float | The character can eat past capacity up to capacity times this. | | `overstuffedDigestBonus` | float | Additional capacity gained on digest when the character was over capacity (scaled by how far over). | | `caloriesToKilogram` | float | How many kilograms each calorie digested becomes. | @@ -90,7 +101,6 @@ This is the main character file where much of the functionality is stored. | `blinkChance` | float, % | Chance each tick to blink. | | `gurgleChance` | float, % | Chance each tick of a gurgle (see `sounds`), scaled by how full the character is. | | `gurgleCapacityMultiplier` | float | Multiplies the gurgle chance at full capacity. Higher capacity = higher gurgle chance. | -| `dialoguePool` | string | The character's base dialogue pool for "How are you feeling?". This is effectively "Stage 1"'s dialogue; each stage should have its own dialogue pool. | ### alternate The alternate spritesheet of the character, if applicable (in Pokemon terms, the "shiny"). @@ -103,14 +113,7 @@ The alternate spritesheet of the character, if applicable (in Pokemon terms, the | `chance` | float, % | Chance of rolling the alternate spritesheet when starting a new game. | ### stages -A "stage" represents each visual change of the character as the weight increases. By default, there's always one stage; each entry in this list adds another. - -| Key | Type | Meaning | -|---|---|---| -| `weight` | float, kg | The weight threshold to reach this stage. | -| `dialoguePool` | string | The stage's dialogue pool for "How are you feeling?". | -| `animationSuffix` | string | Suffix appended to base animation names while this stage is active. Usually `Stage2`, `Stage3`, etc. | -| `area` | int | Switches to this area (from `areas.list`) from this stage on. | +A "stage" represents each visual change of the character as the weight increases. There's always a first stage (`Stage1`). This is a list of the weights (in kg) at which each stage after it (`Stage2`, `Stage3`...) is reached. ### animations The character's animations. Know that a lot of character animations are typically easily played through dialogue; this is just for animations that aren't reliant on that system. These are just the base names. @@ -149,7 +152,7 @@ The areas which will expand based on how full the character is and their progres | `scaleMax` | The scale (in percent) that will be added at maximum. Know that a scale of 100 is the normal scale of an animation. | ### interactTypes, interactAreas -`interactTypes` is an object of interaction types by name (each gets a button on the taskbar), giving each type's defaults. `interactAreas` is a list of the concrete areas on a character which can be interacted with; usually for belly rubs, kisses, etc. Each area names its `type` and takes all of that type's settings, changing whichever it gives itself. +`interactTypes` is an object of interaction types by name, giving each type's defaults. Rub, Smack, Kiss and Turn Around come from common. A type gets a button on the taskbar only if one of the character's areas uses it. `interactAreas` is a list of the concrete areas on a character which can be interacted with; usually for belly rubs, kisses, etc. Each area names its `type` and takes all of that type's settings, changing whichever it gives itself. | Key | Type | Meaning | |---|---|---| @@ -162,8 +165,7 @@ The areas which will expand based on how full the character is and their progres | `cursorActive` | string | The animation the cursor will play when interacting. | | `animation` | string | A character animation to play on interacting, when the area has no dialogue pool. | | `idleOnEnd` | string | The idle animation the character uses after interacting (e.g. turning around). | -| `dialoguePool` | string | The dialogue pool which will be drawn from when interacting. | -| `dialoguePoolFull` | string | Dialogue pool used when the character is over capacity. | +| `dialoguePool` | string | The dialogue pool which will be drawn from when interacting. Over capacity, the same pool with `Full` added is used, if there is one. | | `dialogueCooldown` | float, seconds | Time before interacting can start new dialogue again. | | `digestionClick` | float, % | Digestion added each click. | | `digestionHover` | float, %/second | Digestion added while the cursor moves, held. | @@ -179,33 +181,22 @@ The areas which will expand based on how full the character is and their progres | `digest` | Plays when the character begins digesting (digestion bar full). | | `gurgle` | Ambient sounds that play randomly based on capacity; see `gurgleChance`. | -### areas -"Areas" refers to the backgrounds of the game. Stages can switch between them (see `stages`). - -| Key | Type | Meaning | -|---|---|---| -| `anm2` | path | The anm2 the areas use. | -| `boundsNull` | string | The null setting the world's bounds. Defaults to `Bounds`. | -| `spawnNull` | string | The null items spawn at random points within. If omitted or missing, `boundsNull` is used. | -| `list` | list | The areas. Each can set its own `anm2`, its `gravity` (applied to items' velocities per tick), `friction` (applied when items are grounded or hit walls) and `airResistance`. | +### area +How items move in the background (`gfx/areas/area.anm2`): `gravity` (applied to items' velocities per tick), `friction` (applied when items are grounded or hit walls) and `airResistance`. ### cursor -Determines the cursor appearance and behavior. Without one, the system cursor is used. +The cursor's sounds and particles. Its look is `gfx/cursor/cursor.anm2` (see "Fixed files and names"). | Key | Meaning | |---|---| -| `anm2` | The anm2 file the cursor will use. | -| `animations` | `idle`, `click`, `hover` (over an item), `grab` (an item), `pan` (with middle mouse), `zoom` (with the mouse wheel) and `dispose` (right clicking an item). | | `sounds` | `grab` (grabbing an item), `release` (letting go) and `throw` (letting go while dragging quickly). | | `particles` | `grab`: particles spawned when grabbing an item. | ### menu -Determines menu and general UI appearance and behavior. +The UI's sounds. | Key | Meaning | |---|---| -| `font` | The font the menu will use. | -| `rounding` | The rounding of corners the UI will use. | | `sounds` | `open` and `close` (the taskbar or one of its windows), `hover` and `select` (a widget), and `cheats` (after entering a special code to activate cheats). | ### particles diff --git a/resources/characters/braixen/character.json b/resources/characters/braixen/character.json index ceb2896..f91eb5f 100644 --- a/resources/characters/braixen/character.json +++ b/resources/characters/braixen/character.json @@ -2,123 +2,35 @@ "name": "Braixen", "description": "A fire-wielding, enigmatic Fox Pokémon. You're her apprentice!", "credits": "Art, Animation, Dialogue: ShweetMagnet Sound Effects: Pokemon, skunkwhoplunks Commissioned by @FatGirIsHMU on Twitter", - "render": "gfx/preview/render.png", - "portrait": "gfx/preview/portrait.png", - "anm2": "gfx/character/braixen.anm2", "color": [0.901961, 0.156863, 0.160784], "weight": 32, "weightMax": 250, "capacity": 1000, - "capacityMin": 1000, - "capacityMax": 10000, - "overstuffedMultiplier": 1.5, - "overstuffedDigestBonus": 0.2, - "caloriesToKilogram": 0.001, - "digestionRate": 1.5, - "digestionRateMax": 4.5, - "digestionTime": 2, - "blinkChance": 0.5, - "gurgleChance": 0.05, - "gurgleCapacityMultiplier": 10, - "dialoguePool": "Stage1", - "stages": [ - {"weight": 35, "dialoguePool": "Stage2", "animationSuffix": "Stage2"}, - {"weight": 45, "dialoguePool": "Stage3", "animationSuffix": "Stage3"}, - {"weight": 55, "dialoguePool": "Stage4", "animationSuffix": "Stage4"}, - {"weight": 70, "dialoguePool": "Stage5", "animationSuffix": "Stage5"}, - {"weight": 95, "dialoguePool": "Stage6", "animationSuffix": "Stage6"}, - {"weight": 125, "dialoguePool": "Stage7", "animationSuffix": "Stage7"}, - {"weight": 160, "dialoguePool": "Stage8", "animationSuffix": "Stage8"}, - {"weight": 200, "dialoguePool": "Stage9", "animationSuffix": "Stage9"}, - {"weight": 250, "dialoguePool": "Stage10", "animationSuffix": "Stage10"} - ], - "animations": {"start": "Appear", "idle": "Idle", "idleFull": "NeutralFull", "stageUp": "StageUp"}, - "talk": {"source": "Talk", "destination": "Mouth"}, - "blink": {"source": "Blink", "destination": "Eye"}, - "eatAreas": [ - {"null": "Mouth", "animation": "Eat", "event": "Eat"} - ], - "expandAreas": [ - {"layer": "Torso", "null": "Belly", "scaleMax": 1.25} - ], - "sounds": {"digest": "sfx/digest*.ogg", "gurgle": "sfx/gurgle*.ogg"}, - "interactTypes": { - "Rub": { - "icon": "rub", - "isHold": true, - "cursorHover": "Hover", - "cursorActive": "Rub", - "sounds": ["sfx/rub.ogg"], - "particles": ["Circle"] - }, - "Smack": { - "icon": "smack", - "cursorHover": "SmackHover", - "cursorActive": "Smack", - "dialogueCooldown": 5, - "digestionClick": 0.25, - "scaleAmplitude": 1, - "scaleCycles": 3, - "sounds": "sfx/smack*.ogg", - "particles": ["Circle"] - }, - "Kiss": { - "icon": "kiss", - "cursorHover": "KissHover", - "cursorActive": "Kiss", - "sounds": "sfx/kiss*.ogg", - "particles": ["Circle"] - }, - "Turn Around": { - "icon": "turn", - "sounds": ["sfx/turn.ogg"], - "particles": ["Circle"] - } - }, + "stages": [35, 45, 55, 70, 95, 125, 160, 200, 250], + "animations": {"idle": "Idle"}, + "expandAreas": [{"layer": "Torso", "null": "Belly", "scaleMax": 1.25}], + "interactTypes": {"Smack": {"dialogueCooldown": 5, "digestionClick": 0.25}}, "interactAreas": [ - {"type": "Rub", "null": "Head", "dialoguePool": "HeadRub", "dialoguePoolFull": "HeadRubFull"}, - {"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "dialoguePoolFull": "BellyRubFull", "digestionHover": 6}, - {"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss", "dialoguePoolFull": "MouthKiss"}, - {"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss", "dialoguePoolFull": "BellyKiss"}, - {"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "dialoguePoolFull": "BellySmack"}, + {"type": "Rub", "null": "Head", "dialoguePool": "HeadRub"}, + {"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "digestionHover": 6}, + {"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss"}, + {"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss"}, + {"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack"}, {"type": "Smack", "null": "ButtFront", "layer": "LegFront", "dialoguePool": "ButtSmack"}, {"type": "Smack", "null": "ButtBack", "layer": "LegBack", "idleOnEnd": "IdleBack", "dialoguePool": "ButtSmack"}, - {"type": "Turn Around", "null": "FullBody", "animation": "IdleBack", "idleOnEnd": "IdleBack", "cursorHover": "TurnAround"}, - {"type": "Turn Around", "null": "FullBodyBack", "animation": "Idle", "idleOnEnd": "Idle", "cursorHover": "TurnAroundBack"} - ], - "areas": { - "anm2": "gfx/areas/area.anm2", - "boundsNull": "Bounds", - "spawnNull": "ItemSpawn", - "list": [ - {"gravity": 0.95, "friction": 0.75, "airResistance": 0.975} - ] - }, - "cursor": { - "anm2": "gfx/cursor/cursor.anm2", - "animations": {"idle": "Default", "click": "Click", "hover": "Hover", "grab": "Grab", "pan": "Pan", "zoom": "Zoom", "dispose": "Dispose"}, - "sounds": { - "grab": ["sfx/grab.ogg"], - "release": ["sfx/release.ogg"], - "throw": "sfx/throw*.ogg" + { + "type": "Turn Around", + "null": "FullBody", + "animation": "IdleBack", + "idleOnEnd": "IdleBack", + "cursorHover": "TurnAround" }, - "particles": { - "grab": ["Circle"] + { + "type": "Turn Around", + "null": "FullBodyBack", + "animation": "Idle", + "idleOnEnd": "Idle", + "cursorHover": "TurnAroundBack" } - }, - "menu": { - "font": "font/font.ttf", - "rounding": 10.0, - "sounds": { - "open": ["sfx/open.ogg"], - "close": ["sfx/close.ogg"], - "hover": "sfx/hover*.ogg", - "select": ["sfx/select.ogg"], - "cheats": ["sfx/cheatsActivated.ogg"] - } - }, - "particles": { - "Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"}, - "Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"} - } + ] } diff --git a/resources/characters/braixen/font/font.ttf b/resources/characters/braixen/font/font.ttf deleted file mode 100644 index 3180658..0000000 Binary files a/resources/characters/braixen/font/font.ttf and /dev/null differ diff --git a/resources/characters/braixen/gfx/character/braixen.anm2 b/resources/characters/braixen/gfx/character/character.anm2 similarity index 100% rename from resources/characters/braixen/gfx/character/braixen.anm2 rename to resources/characters/braixen/gfx/character/character.anm2 diff --git a/resources/characters/mini/character.json b/resources/characters/mini/character.json index e8a2b83..606d19e 100644 --- a/resources/characters/mini/character.json +++ b/resources/characters/mini/character.json @@ -2,109 +2,18 @@ "name": "Princess Mini", "description": "A sprightly, pampered, petite princess. Don't call her \"Mint\"!", "credits": "Art, Animation, Dialogue: ShweetMagnet Character: 2x/MerchantAnon Sound Effects: JC Sounds (CC-BY 4.0), artisticdude (CC0), Kronbits (CC0), Vehicle (CC0), pixabay.org, ShweetMagnet Font: DPComic", - "render": "gfx/preview/render.png", - "portrait": "gfx/preview/portrait.png", - "anm2": "gfx/character/mini.anm2", "color": [0.188, 0.651, 0.424], "weight": 40, "weightMax": 100, "capacity": 1250, - "capacityMin": 1250, - "capacityMax": 10000, - "overstuffedMultiplier": 1.5, - "overstuffedDigestBonus": 0.2, - "caloriesToKilogram": 0.001, - "digestionRate": 1.5, - "digestionRateMax": 4.5, - "digestionTime": 2, - "blinkChance": 0.5, - "gurgleChance": 0.05, - "gurgleCapacityMultiplier": 10, - "dialoguePool": "Stage1", - "stages": [ - {"weight": 45, "dialoguePool": "Stage2", "animationSuffix": "Stage2"}, - {"weight": 60, "dialoguePool": "Stage3", "animationSuffix": "Stage3"}, - {"weight": 70, "dialoguePool": "Stage4", "animationSuffix": "Stage4"}, - {"weight": 90, "dialoguePool": "Stage5", "animationSuffix": "Stage5"} - ], - "animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"}, - "talk": {"source": "Talk", "destination": "Mouth"}, - "blink": {"source": "Blink", "destination": "Eye"}, - "eatAreas": [ - {"null": "Mouth", "animation": "Eat", "event": "Eat"} - ], - "expandAreas": [ - {"layer": "Torso", "null": "Belly", "scaleMax": 2.5} - ], - "sounds": {"digest": "sfx/digest*.ogg", "gurgle": "sfx/gurgle*.ogg"}, - "interactTypes": { - "Rub": { - "icon": "rub", - "isHold": true, - "cursorHover": "Hover", - "cursorActive": "Rub", - "sounds": ["sfx/rub.ogg"], - "particles": ["Circle"] - }, - "Smack": { - "icon": "smack", - "cursorHover": "SmackHover", - "cursorActive": "Smack", - "dialogueCooldown": 0.6667, - "digestionClick": 0.25, - "scaleAmplitude": 1, - "scaleCycles": 3, - "sounds": "sfx/smack*.ogg", - "particles": ["Circle"] - }, - "Kiss": { - "icon": "kiss", - "cursorHover": "KissHover", - "cursorActive": "Kiss", - "sounds": "sfx/kiss*.ogg", - "particles": ["Circle"] - } - }, + "stages": [45, 60, 70, 90], + "expandAreas": [{"layer": "Torso", "null": "Belly", "scaleMax": 2.5}], + "interactTypes": {"Smack": {"digestionClick": 0.25}}, "interactAreas": [ - {"type": "Rub", "null": "Head", "dialoguePool": "HeadRub", "dialoguePoolFull": "HeadRubFull"}, - {"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "dialoguePoolFull": "BellyRubFull", "digestionHover": 6}, - {"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss", "dialoguePoolFull": "MouthKiss"}, - {"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss", "dialoguePoolFull": "BellyKiss"}, - {"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "dialoguePoolFull": "BellySmack"} - ], - "areas": { - "anm2": "gfx/areas/area.anm2", - "boundsNull": "Bounds", - "spawnNull": "ItemSpawn", - "list": [ - {"gravity": 0.95, "friction": 0.75, "airResistance": 0.975} - ] - }, - "cursor": { - "anm2": "gfx/cursor/cursor.anm2", - "animations": {"idle": "Default", "click": "Click", "hover": "Hover", "grab": "Grab", "pan": "Pan", "zoom": "Zoom", "dispose": "Dispose"}, - "sounds": { - "grab": ["sfx/grab.ogg"], - "release": ["sfx/release.ogg"], - "throw": "sfx/throw*.ogg" - }, - "particles": { - "grab": ["Circle"] - } - }, - "menu": { - "font": "font/font.ttf", - "rounding": 10.0, - "sounds": { - "open": ["sfx/open.ogg"], - "close": ["sfx/close.ogg"], - "hover": "sfx/hover*.ogg", - "select": ["sfx/select.ogg"], - "cheats": ["sfx/cheatsActivated.ogg"] - } - }, - "particles": { - "Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"}, - "Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"} - } + {"type": "Rub", "null": "Head", "dialoguePool": "HeadRub"}, + {"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "digestionHover": 6}, + {"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss"}, + {"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss"}, + {"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack"} + ] } diff --git a/resources/characters/mini/font/font.ttf b/resources/characters/mini/font/font.ttf deleted file mode 100644 index a0776ae..0000000 Binary files a/resources/characters/mini/font/font.ttf and /dev/null differ diff --git a/resources/characters/mini/gfx/character/mini.anm2 b/resources/characters/mini/gfx/character/character.anm2 similarity index 100% rename from resources/characters/mini/gfx/character/mini.anm2 rename to resources/characters/mini/gfx/character/character.anm2 diff --git a/resources/characters/snivy/character.json b/resources/characters/snivy/character.json index ee2ea46..29ea3a9 100644 --- a/resources/characters/snivy/character.json +++ b/resources/characters/snivy/character.json @@ -2,128 +2,48 @@ "name": "Snivy", "description": "A Grass Snake Pokémon with rather large ambitions.", "credits": "Art, Animation, Dialogue: ShweetMagnet Character - Pokémon Sound Effects - Pokémon; ShweetMagnet", - "render": "gfx/preview/render.png", - "portrait": "gfx/preview/portrait.png", - "anm2": "gfx/character/snivy.anm2", "color": [0.12, 0.515, 0.115], "weight": 8.1, "weightMax": 200, "capacity": 300, - "capacityMin": 300, - "capacityMax": 10000, - "overstuffedMultiplier": 1.5, "overstuffedDigestBonus": 0.25, "caloriesToKilogram": 0.0005, - "digestionRate": 1.5, - "digestionRateMax": 4.5, - "digestionTime": 2, - "blinkChance": 0.5, - "gurgleChance": 0.05, - "gurgleCapacityMultiplier": 10, - "dialoguePool": "Stage1", "alternate": {"texture": "gfx/character/snivy_alt.png", "sound": "sfx/shiny.ogg", "chance": 0.001}, - "stages": [ - {"weight": 10, "dialoguePool": "Stage2", "animationSuffix": "Stage2"}, - {"weight": 12.5, "dialoguePool": "Stage3", "animationSuffix": "Stage3"}, - {"weight": 17.5, "dialoguePool": "Stage4", "animationSuffix": "Stage4"}, - {"weight": 27.5, "dialoguePool": "Stage5", "animationSuffix": "Stage5"}, - {"weight": 40, "dialoguePool": "Stage6", "animationSuffix": "Stage6"}, - {"weight": 55, "dialoguePool": "Stage7", "animationSuffix": "Stage7"}, - {"weight": 75, "dialoguePool": "Stage8", "animationSuffix": "Stage8"}, - {"weight": 100, "dialoguePool": "Stage9", "animationSuffix": "Stage9"}, - {"weight": 130, "dialoguePool": "Stage10", "animationSuffix": "Stage10"} - ], - "animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"}, - "talk": {"source": "Talk", "destination": "Mouth"}, - "blink": {"source": "Blink", "destination": "Head"}, - "eatAreas": [ - {"null": "Mouth", "animation": "Eat", "event": "Eat"} - ], - "expandAreas": [ - {"layer": "Torso", "null": "Belly", "scaleMax": 7.5}, - {"layer": "Tail", "null": "Tail", "scaleMax": 7.5} - ], - "sounds": {"digest": "sfx/digest*.ogg", "gurgle": "sfx/gurgle*.ogg"}, - "interactTypes": { - "Rub": { - "icon": "rub", - "isHold": true, - "cursorHover": "Hover", - "cursorActive": "Rub", - "sounds": ["sfx/rub.ogg"], - "particles": ["Circle"] - }, - "Smack": { - "icon": "smack", - "cursorHover": "SmackHover", - "cursorActive": "Smack", - "dialogueCooldown": 0.6667, - "scaleAmplitude": 5, - "scaleCycles": 3, - "sounds": "sfx/smack*.ogg", - "particles": ["Circle"] - }, - "Kiss": { - "icon": "kiss", - "cursorHover": "KissHover", - "cursorActive": "Kiss", - "sounds": "sfx/kiss*.ogg", - "particles": ["Circle"] - }, - "Turn Around": { - "icon": "turn", - "sounds": ["sfx/turn.ogg"], - "particles": ["Circle"] - } - }, + "stages": [10, 12.5, 17.5, 27.5, 40, 55, 75, 100, 130], + "blink": {"destination": "Head"}, + "expandAreas": [{"layer": "Torso", "null": "Belly", "scaleMax": 7.5}, {"layer": "Tail", "null": "Tail", "scaleMax": 7.5}], + "interactTypes": {"Smack": {"scaleAmplitude": 5}}, "interactAreas": [ - {"type": "Rub", "null": "Head", "dialoguePool": "HeadRub", "dialoguePoolFull": "HeadRubFull"}, - {"type": "Rub", "null": "HeadBack", "idleOnEnd": "NeutralBack", "dialoguePool": "HeadRubBack", "dialoguePoolFull": "HeadRubBackFull"}, - {"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "dialoguePoolFull": "BellyRubFull", "digestionHover": 6}, - {"type": "Rub", "null": "Tail", "cursorActive": "Grab", "dialoguePool": "TailRub", "dialoguePoolFull": "TailRubFull"}, - {"type": "Rub", "null": "TailBack", "idleOnEnd": "NeutralBack", "cursorActive": "Grab", "dialoguePool": "TailRubBack", "dialoguePoolFull": "TailRubBackFull"}, - {"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss", "dialoguePoolFull": "MouthKiss"}, - {"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss", "dialoguePoolFull": "BellyKiss"}, - {"type": "Kiss", "null": "Tail", "dialoguePool": "TailKiss", "dialoguePoolFull": "TailKiss"}, - {"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "dialoguePoolFull": "BellySmack", "digestionClick": 0.25}, - {"type": "Smack", "null": "Tail", "layer": "Tail", "dialoguePool": "TailSmack", "dialoguePoolFull": "TailSmack"}, - {"type": "Smack", "null": "TailBack", "layer": "Tail", "idleOnEnd": "NeutralBack", "dialoguePool": "TailSmackBack", "dialoguePoolFull": "TailSmackBack"}, - {"type": "Turn Around", "null": "FullBody", "animation": "NeutralBack", "idleOnEnd": "NeutralBack", "cursorHover": "TurnAround"}, - {"type": "Turn Around", "null": "FullBodyBack", "animation": "Neutral", "idleOnEnd": "Neutral", "cursorHover": "TurnAroundBack"} - ], - "areas": { - "anm2": "gfx/areas/area.anm2", - "boundsNull": "Bounds", - "spawnNull": "ItemSpawn", - "list": [ - {"gravity": 0.95, "friction": 0.75, "airResistance": 0.975} - ] - }, - "cursor": { - "anm2": "gfx/cursor/cursor.anm2", - "animations": {"idle": "Default", "click": "Click", "hover": "Hover", "grab": "Grab", "pan": "Pan", "zoom": "Zoom", "dispose": "Dispose"}, - "sounds": { - "grab": ["sfx/grab.ogg"], - "release": ["sfx/release.ogg"], - "throw": "sfx/throw*.ogg" + {"type": "Rub", "null": "Head", "dialoguePool": "HeadRub"}, + {"type": "Rub", "null": "HeadBack", "idleOnEnd": "NeutralBack", "dialoguePool": "HeadRubBack"}, + {"type": "Rub", "null": "Belly", "cursorActive": "Grab", "dialoguePool": "BellyRub", "digestionHover": 6}, + {"type": "Rub", "null": "Tail", "cursorActive": "Grab", "dialoguePool": "TailRub"}, + { + "type": "Rub", + "null": "TailBack", + "idleOnEnd": "NeutralBack", + "cursorActive": "Grab", + "dialoguePool": "TailRubBack" }, - "particles": { - "grab": ["Circle"] + {"type": "Kiss", "null": "Mouth", "dialoguePool": "MouthKiss"}, + {"type": "Kiss", "null": "Belly", "dialoguePool": "BellyKiss"}, + {"type": "Kiss", "null": "Tail", "dialoguePool": "TailKiss"}, + {"type": "Smack", "null": "Belly", "layer": "Torso", "dialoguePool": "BellySmack", "digestionClick": 0.25}, + {"type": "Smack", "null": "Tail", "layer": "Tail", "dialoguePool": "TailSmack"}, + {"type": "Smack", "null": "TailBack", "layer": "Tail", "idleOnEnd": "NeutralBack", "dialoguePool": "TailSmackBack"}, + { + "type": "Turn Around", + "null": "FullBody", + "animation": "NeutralBack", + "idleOnEnd": "NeutralBack", + "cursorHover": "TurnAround" + }, + { + "type": "Turn Around", + "null": "FullBodyBack", + "animation": "Neutral", + "idleOnEnd": "Neutral", + "cursorHover": "TurnAroundBack" } - }, - "menu": { - "font": "font/font.ttf", - "rounding": 10.0, - "sounds": { - "open": ["sfx/open.ogg"], - "close": ["sfx/close.ogg"], - "hover": "sfx/hover*.ogg", - "select": ["sfx/select.ogg"], - "cheats": ["sfx/cheatsActivated.ogg"] - } - }, - "particles": { - "Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"}, - "Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"} - } + ] } diff --git a/resources/characters/snivy/font/font.ttf b/resources/characters/snivy/font/font.ttf deleted file mode 100644 index 63b83f0..0000000 Binary files a/resources/characters/snivy/font/font.ttf and /dev/null differ diff --git a/resources/characters/snivy/gfx/character/snivy.anm2 b/resources/characters/snivy/gfx/character/character.anm2 similarity index 100% rename from resources/characters/snivy/gfx/character/snivy.anm2 rename to resources/characters/snivy/gfx/character/character.anm2 diff --git a/resources/common/character.json b/resources/common/character.json new file mode 100644 index 0000000..4e239b0 --- /dev/null +++ b/resources/common/character.json @@ -0,0 +1,63 @@ +{ + "capacityMax": 10000, + "overstuffedMultiplier": 1.5, + "overstuffedDigestBonus": 0.2, + "caloriesToKilogram": 0.001, + "digestionRate": 1.5, + "digestionRateMax": 4.5, + "digestionTime": 2, + "blinkChance": 0.5, + "gurgleChance": 0.05, + "gurgleCapacityMultiplier": 10, + "animations": {"start": "Appear", "idle": "Neutral", "idleFull": "NeutralFull", "stageUp": "StageUp"}, + "talk": {"source": "Talk", "destination": "Mouth"}, + "blink": {"source": "Blink", "destination": "Eye"}, + "eatAreas": [{"animation": "Eat", "event": "Eat", "null": "Mouth"}], + "sounds": {"digest": "sfx/digest*.ogg", "gurgle": "sfx/gurgle*.ogg"}, + "interactTypes": { + "Rub": { + "icon": "rub", + "isHold": true, + "cursorHover": "Hover", + "cursorActive": "Rub", + "sounds": ["sfx/rub.ogg"], + "particles": ["Circle"] + }, + "Smack": { + "icon": "smack", + "cursorHover": "SmackHover", + "cursorActive": "Smack", + "dialogueCooldown": 0.6667, + "scaleAmplitude": 1, + "scaleCycles": 3, + "sounds": "sfx/smack*.ogg", + "particles": ["Circle"] + }, + "Kiss": { + "icon": "kiss", + "cursorHover": "KissHover", + "cursorActive": "Kiss", + "sounds": "sfx/kiss*.ogg", + "particles": ["Circle"] + }, + "Turn Around": {"icon": "turn", "sounds": ["sfx/turn.ogg"], "particles": ["Circle"]} + }, + "cursor": { + "sounds": {"grab": ["sfx/grab.ogg"], "release": ["sfx/release.ogg"], "throw": "sfx/throw*.ogg"}, + "particles": {"grab": ["Circle"]} + }, + "menu": { + "sounds": { + "open": ["sfx/open.ogg"], + "close": ["sfx/close.ogg"], + "hover": "sfx/hover*.ogg", + "select": ["sfx/select.ogg"], + "cheats": ["sfx/cheatsActivated.ogg"] + } + }, + "particles": { + "Circle": {"anm2": "gfx/particle/circle.anm2", "animation": "Main"}, + "Puff": {"anm2": "gfx/particle/puff.anm2", "animation": "Main"} + }, + "area": {"gravity": 0.95, "friction": 0.75, "airResistance": 0.975} +} diff --git a/resources/font/LICENSE.txt b/resources/font/LICENSE.txt new file mode 100644 index 0000000..46a9a7c --- /dev/null +++ b/resources/font/LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2018 The Mali Project Authors (https://github.com/cadsondemak/Mali) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/resources/font/font.ttf b/resources/font/font.ttf index 4bac02f..089cdfd 100644 Binary files a/resources/font/font.ttf and b/resources/font/font.ttf differ diff --git a/src/body.cpp b/src/body.cpp index d9597e8..d98bbe8 100644 --- a/src/body.cpp +++ b/src/body.cpp @@ -44,7 +44,7 @@ void Body::set(Character& played, Audio& sound, Game game) void Body::stage_set(int index) { stage = index; - suffix = stage > 0 && stage <= (int)character->stages.size() ? character->stages[stage - 1].animationSuffix : ""; + suffix = stage > 0 ? character->stage_name_get(stage) : ""; } float Body::capacity_max_get() @@ -65,21 +65,21 @@ bool Body::is_over_capacity() int Body::stage_get(float weight) { - std::vector& stages = character->stages; + std::vector& stages = character->stages; if (stages.empty() || weight <= character->traits.weight) return 0; for (int i = 0; i < (int)stages.size(); i++) - if (weight < stages[i].weight) + if (weight < stages[i]) return i; return (int)stages.size(); } float Body::stage_threshold_get(int index) { - std::vector& stages = character->stages; + std::vector& stages = character->stages; if (index <= 0 || stages.empty()) return character->traits.weight; - return stages[std::min(index, (int)stages.size()) - 1].weight; + return stages[std::min(index, (int)stages.size()) - 1]; } float Body::stage_progress_get() @@ -132,7 +132,7 @@ void Body::values_clamp() { Traits& traits = character->traits; stats.weight = value_clamp(stats.weight, traits.weight, traits.weightMax); - stats.capacity = value_clamp(stats.capacity, traits.capacityMin, traits.capacityMax); + stats.capacity = value_clamp(stats.capacity, traits.capacity, traits.capacityMax); stats.digestionRate = value_clamp(stats.digestionRate, traits.digestionRateMin, traits.digestionRateMax); stats.calories = value_clamp(stats.calories, 0, std::max(0.0f, capacity_max_get())); stats.digestionProgress = value_clamp(stats.digestionProgress, 0, DIGESTION_MAX); @@ -195,7 +195,7 @@ void Body::update(std::vector& events) float over = stats.calories - stats.capacity; float overPercent = over / (capacity_max_get() - stats.capacity); stats.capacity = value_clamp(stats.capacity + overPercent * traits.overstuffedDigestBonus * over, - traits.capacityMin, traits.capacityMax); + traits.capacity, traits.capacityMax); } stats.calories = 0; if (int nextStage = stage_get(nextWeight); nextStage > stage_get(stats.weight)) diff --git a/src/character.cpp b/src/character.cpp index 5fe5d78..5d6de2d 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1,10 +1,16 @@ #include "character.h" #include "json.h" -#include -constexpr float FONT_SIZE = 20; +constexpr const char* AREA_PATH = "gfx/areas/area.anm2"; +constexpr const char* BOUNDS_NULL = "Bounds"; constexpr const char* COMMON_FOLDER = "common/"; +constexpr const char* CURSOR_PATH = "gfx/cursor/cursor.anm2"; +constexpr const char* DATA_FILE = "character.json"; constexpr const char* ICONS_PATH = "gfx/ui/icons.anm2"; +constexpr const char* PORTRAIT_PATH = "gfx/preview/portrait.png"; +constexpr const char* RENDER_PATH = "gfx/preview/render.png"; +constexpr const char* SPAWN_NULL = "ItemSpawn"; +constexpr const char* STAGE_PREFIX = "Stage"; constexpr const char* TAG_NAMES[] = {"animation", "big", "color", "delay", "screenshake", "shake", "silent", "small", "sound", "wave"}; constexpr const char* USE_NAMES[] = {"", "edible", "tool"}; @@ -69,7 +75,7 @@ int Catalog::item_get(const std::string& id) void Character::preview_load(const std::string& path, Renderer& renderer) { Json json; - json.load(path + "character.json"); + json.load(path + DATA_FILE); if (!(isValid = json.isValid)) return; object root = json.root; @@ -83,15 +89,17 @@ void Character::preview_load(const std::string& path, Renderer& renderer) credits = json_string(root, "credits"); color = json_color(root, "color", color); traits.weight = json_float(root, "weight"); - anm2Path = json_string(root, "anm2"); - portrait = root["portrait"].error() ? nullptr : renderer.texture_get(file_find(roots, json_string(root, "portrait"))); - render = root["render"].error() ? nullptr : renderer.texture_get(file_find(roots, json_string(root, "render"))); + auto texture_get = [&](const char* relative) { + std::string file = folder + relative; + return SDL_GetPathInfo(file.c_str(), nullptr) ? renderer.texture_get(file) : nullptr; + }; + portrait = texture_get(PORTRAIT_PATH); + render = texture_get(RENDER_PATH); for (element entry : json_array(root, "stages")) { - object fields; - if (!entry.get(fields)) - stages.push_back({json_string(fields, "animationSuffix"), json_string(fields, "dialoguePool"), - json_float(fields, "weight"), json_int(fields, "area", -1)}); + double weight; + if (!entry.get(weight)) + stages.push_back((float)weight); } } @@ -112,14 +120,13 @@ static void interact_parse(object source, InteractArea& area, Character& charact { json_keys_check(source, {"type", "null", "layer", "icon", "isHold", "animation", "idleOnEnd", "cursorHover", "cursorActive", - "dialoguePool", "dialoguePoolFull", "dialogueCooldown", "digestionClick", "digestionHover", + "dialoguePool", "dialogueCooldown", "digestionClick", "digestionHover", "scaleAmplitude", "scaleCycles", "scaleTime", "sounds", "particles"}, "an interact area"); area.animation = json_string(source, "animation", area.animation); area.cursorActive = json_string(source, "cursorActive", area.cursorActive); area.cursorHover = json_string(source, "cursorHover", area.cursorHover); area.dialoguePool = json_string(source, "dialoguePool", area.dialoguePool); - area.dialoguePoolFull = json_string(source, "dialoguePoolFull", area.dialoguePoolFull); area.icon = json_string(source, "icon", area.icon); area.idleOnEnd = json_string(source, "idleOnEnd", area.idleOnEnd); area.digestionClick = json_float(source, "digestionClick", area.digestionClick); @@ -142,30 +149,29 @@ static void interact_parse(object source, InteractArea& area, Character& charact static void character_parse(object root, Character& character, Renderer& renderer, Audio& audio) { json_keys_check(root, - {"id", "name", "description", "credits", "render", "portrait", "anm2", "color", "weight", "weightMax", - "capacity", "capacityMin", "capacityMax", "overstuffedMultiplier", "overstuffedDigestBonus", - "caloriesToKilogram", "digestionRate", "digestionRateMin", "digestionRateMax", "digestionTime", - "blinkChance", "gurgleChance", "gurgleCapacityMultiplier", "dialoguePool", "alternate", "stages", - "animations", "talk", "blink", "eatAreas", "expandAreas", "sounds", "interactTypes", - "interactAreas", "areas", "cursor", "menu", "particles"}, + {"id", "name", "description", "credits", "color", "weight", "weightMax", "capacity", "capacityMax", + "overstuffedMultiplier", "overstuffedDigestBonus", "caloriesToKilogram", "digestionRate", + "digestionRateMin", "digestionRateMax", "digestionTime", "blinkChance", "gurgleChance", + "gurgleCapacityMultiplier", "alternate", "stages", "animations", "talk", "blink", "eatAreas", + "expandAreas", "sounds", "interactTypes", "interactAreas", "area", "cursor", "menu", "particles"}, "character.json"); - character.anm2 = character.anm2_get(character.anm2Path, renderer, audio); Traits& traits = character.traits; - traits.weightMax = json_float(root, "weightMax", traits.weight); - traits.capacity = json_float(root, "capacity"); - traits.capacityMin = json_float(root, "capacityMin"); - traits.capacityMax = json_float(root, "capacityMax", traits.capacity); - traits.overstuffedMultiplier = json_float(root, "overstuffedMultiplier", 1); - traits.overstuffedDigestBonus = json_float(root, "overstuffedDigestBonus"); - traits.caloriesToKilogram = json_float(root, "caloriesToKilogram"); - traits.digestionRate = json_float(root, "digestionRate") / TICK_RATE; - traits.digestionRateMin = json_float(root, "digestionRateMin") / TICK_RATE; - traits.digestionRateMax = json_float(root, "digestionRateMax", traits.digestionRate * TICK_RATE) / TICK_RATE; - traits.digestionTime = ticks_get(json_float(root, "digestionTime")); - traits.blinkChance = json_float(root, "blinkChance"); - traits.gurgleChance = json_float(root, "gurgleChance"); - traits.gurgleCapacityMultiplier = json_float(root, "gurgleCapacityMultiplier"); - character.dialoguePool = json_string(root, "dialoguePool"); + auto float_read = [&](object source, const char* key, float& value, float scale = 1) { + value = json_float(source, key, value * scale) / scale; + }; + float_read(root, "weightMax", traits.weightMax); + float_read(root, "capacity", traits.capacity); + float_read(root, "capacityMax", traits.capacityMax); + float_read(root, "overstuffedMultiplier", traits.overstuffedMultiplier); + float_read(root, "overstuffedDigestBonus", traits.overstuffedDigestBonus); + float_read(root, "caloriesToKilogram", traits.caloriesToKilogram); + float_read(root, "digestionRate", traits.digestionRate, TICK_RATE); + float_read(root, "digestionRateMin", traits.digestionRateMin, TICK_RATE); + float_read(root, "digestionRateMax", traits.digestionRateMax, TICK_RATE); + float_read(root, "blinkChance", traits.blinkChance); + float_read(root, "gurgleChance", traits.gurgleChance); + float_read(root, "gurgleCapacityMultiplier", traits.gurgleCapacityMultiplier); + traits.digestionTime = ticks_get(json_float(root, "digestionTime", traits.digestionTime / TICK_RATE)); object alternate = json_object(root, "alternate"); json_keys_check(alternate, {"texture", "sound", "chance", "spritesheet"}, "alternate"); @@ -173,13 +179,14 @@ static void character_parse(object root, Character& character, Renderer& rendere character.alternate.texture = renderer.texture_get(file_find(character.roots, json_string(alternate, "texture"))); if (!alternate["sound"].error()) character.alternate.sound = audio.sound_get(file_find(character.roots, json_string(alternate, "sound"))); - character.alternate.chance = json_float(alternate, "chance"); - character.alternate.sheet = json_int(alternate, "spritesheet"); + float_read(alternate, "chance", character.alternate.chance); + character.alternate.sheet = json_int(alternate, "spritesheet", character.alternate.sheet); object animations = json_object(root, "animations"); json_keys_check(animations, {"start", "idle", "idleFull", "stageUp"}, "animations"); - character.animations = {json_string(animations, "idle"), json_string(animations, "idleFull"), - json_string(animations, "stageUp"), json_string(animations, "start")}; + Animations& names = character.animations; + names = {json_string(animations, "idle", names.idle), json_string(animations, "idleFull", names.idleFull), + json_string(animations, "stageUp", names.stageUp), json_string(animations, "start", names.start)}; Anm2* anm2 = character.anm2; auto layer_get = [&](object source, const char* key) { @@ -188,20 +195,17 @@ static void character_parse(object root, Character& character, Renderer& rendere auto null_get = [&](object source, const char* key) { return anm2 ? anm2->null_get(json_string(source, key)) : -1; }; - auto swap_get = [&](const char* key) { - object swap = json_object(root, key); - json_keys_check(swap, {"source", "destination"}, key); - return Swap{layer_get(swap, "destination"), layer_get(swap, "source")}; + auto swap_read = [&](const char* key, Swap& swap) { + object fields = json_object(root, key); + json_keys_check(fields, {"source", "destination"}, key); + swap.source = fields["source"].error() ? swap.source : layer_get(fields, "source"); + swap.destination = fields["destination"].error() ? swap.destination : layer_get(fields, "destination"); }; - character.talk = swap_get("talk"); - character.blink = swap_get("blink"); - for (element entry : json_array(root, "stages")) - { - object fields; - if (!entry.get(fields)) - json_keys_check(fields, {"weight", "dialoguePool", "animationSuffix", "area"}, "a stage"); - } + swap_read("talk", character.talk); + swap_read("blink", character.blink); + if (!root["eatAreas"].error()) + character.eatAreas.clear(); for (element entry : json_array(root, "eatAreas")) { object fields; @@ -211,6 +215,8 @@ static void character_parse(object root, Character& character, Renderer& rendere character.eatAreas.push_back({json_string(fields, "animation"), anm2 ? anm2->event_get(json_string(fields, "event")) : -1, null_get(fields, "null")}); } + if (!root["expandAreas"].error()) + character.expandAreas.clear(); for (element entry : json_array(root, "expandAreas")) { object fields; @@ -221,81 +227,47 @@ static void character_parse(object root, Character& character, Renderer& rendere {json_float(fields, "scaleMax"), layer_get(fields, "layer"), null_get(fields, "null")}); } - std::vector typeNames; + std::vector& types = character.interactTypes; + auto type_find = [&](std::string_view name) { + return std::find_if(types.begin(), types.end(), [&](InteractArea& type) { return type.name == name; }); + }; for (auto [name, value] : json_object(root, "interactTypes")) { object fields; if (value.get(fields)) continue; - InteractArea& type = character.interactTypes.emplace_back(); + auto found = type_find(name); + InteractArea& type = found == types.end() ? types.emplace_back() : *found; interact_parse(fields, type, character, audio); type.name = name; - type.type = (int)typeNames.size(); - typeNames.emplace_back(name); + type.type = (int)(&type - types.data()); } + if (!root["interactAreas"].error()) + character.interactAreas.clear(); for (element entry : json_array(root, "interactAreas")) { object fields; if (entry.get(fields)) continue; - auto type = std::find(typeNames.begin(), typeNames.end(), json_string(fields, "type")); - InteractArea area = type == typeNames.end() ? InteractArea{} : character.interactTypes[type - typeNames.begin()]; + auto type = type_find(json_string(fields, "type")); + InteractArea area = type == types.end() ? InteractArea{} : *type; interact_parse(fields, area, character, audio); if (area.null >= 0) character.interactAreas.push_back(area); } - object areas = json_object(root, "areas"); - json_keys_check(areas, {"anm2", "boundsNull", "spawnNull", "list"}, "areas"); - Anm2* areaAnm2 = character.anm2_get(json_string(areas, "anm2"), renderer, audio); - std::string boundsNull = json_string(areas, "boundsNull", "Bounds"); - std::string spawnNull = json_string(areas, "spawnNull", boundsNull); - for (element entry : json_array(areas, "list")) - { - object fields; - if (entry.get(fields)) - continue; - json_keys_check(fields, {"anm2", "gravity", "friction", "airResistance"}, "an area"); - Area& area = character.areas.emplace_back(); - area.anm2 = fields["anm2"].error() ? areaAnm2 : character.anm2_get(json_string(fields, "anm2"), renderer, audio); - area.gravity = json_float(fields, "gravity", area.gravity); - area.friction = json_float(fields, "friction", area.friction); - area.airResistance = json_float(fields, "airResistance", area.airResistance); - if (!area.anm2) - continue; - Actor actor(area.anm2); - area.bounds = actor.null_rect_get(area.anm2->null_get(boundsNull), -1, 0); - area.spawn = actor.null_rect_get(area.anm2->null_get(spawnNull), -1, 0); - area.spawn = is_rect_valid(area.spawn) ? area.spawn : area.bounds; - } + object area = json_object(root, "area"); + json_keys_check(area, {"gravity", "friction", "airResistance"}, "area"); + float_read(area, "gravity", character.area.gravity); + float_read(area, "friction", character.area.friction); + float_read(area, "airResistance", character.area.airResistance); object cursor = json_object(root, "cursor"); - object cursorAnimations = json_object(cursor, "animations"); - json_keys_check(cursor, {"anm2", "animations", "sounds", "particles"}, "cursor"); - json_keys_check(cursorAnimations, {"idle", "click", "hover", "grab", "pan", "zoom", "dispose"}, "cursor animations"); - auto cursor_get = [&](const char* key) { - return json_string(cursorAnimations, key); - }; - character.cursor = {cursor_get("click"), - cursor_get("dispose"), - cursor_get("grab"), - cursor_get("hover"), - cursor_get("idle"), - cursor_get("pan"), - cursor_get("zoom"), - json_strings(json_object(cursor, "particles"), "grab"), - character.anm2_get(json_string(cursor, "anm2"), renderer, audio)}; - - object menu = json_object(root, "menu"); - json_keys_check(menu, {"font", "rounding", "sounds"}, "menu"); - auto font_get = [&](const char* key) { - std::string path = json_string(menu, key); - return path.empty() ? nullptr - : ImGui::GetIO().Fonts->AddFontFromFileTTF(file_find(character.roots, path).c_str(), - FONT_SIZE); - }; - character.menu = {font_get("font"), json_float(menu, "rounding")}; - character.icons = character.anm2_get(ICONS_PATH, renderer, audio); + object cursorParticles = json_object(cursor, "particles"); + json_keys_check(cursor, {"sounds", "particles"}, "cursor"); + if (!cursorParticles["grab"].error()) + character.cursor.grabParticles = json_strings(cursorParticles, "grab"); + json_keys_check(json_object(root, "menu"), {"sounds"}, "menu"); for (auto [name, value] : json_object(root, "particles")) { @@ -308,6 +280,27 @@ static void character_parse(object root, Character& character, Renderer& rendere } } +// Drops the interaction types no area uses, so a character only gets buttons for what it has. +static void interact_types_trim(Character& character) +{ + std::vector used; + for (InteractArea& type : character.interactTypes) + { + bool isUsed = false; + for (InteractArea& area : character.interactAreas) + if (area.type == type.type) + { + area.type = (int)used.size(); + isUsed = true; + } + if (!isUsed) + continue; + used.push_back(type); + used.back().type = (int)used.size() - 1; + } + character.interactTypes = used; +} + static void items_parse(object root, Character& character, Renderer& renderer, Audio& audio) { json_keys_check(root, @@ -503,8 +496,9 @@ static void sounds_parse(object root, DataFile file, Character& character, Audio { const SoundSource& source = SOUND_SOURCES[i]; object section = *source.section ? json_object(root, source.section) : root; - if (source.file == file) - character.sounds[i] = audio.sounds_get(character.roots, json_strings(json_object(section, "sounds"), source.key)); + object sounds = json_object(section, "sounds"); + if (source.file == file && !sounds[source.key].error()) + character.sounds[i] = audio.sounds_get(character.roots, json_strings(sounds, source.key)); } } @@ -513,13 +507,27 @@ void Character::load(Renderer& renderer, Audio& audio) if (isLoaded) return; isLoaded = true; - Json json; - json.load(folder + "character.json"); - if (json.isValid) + anm2 = anm2_get(ANM2_PATH, renderer, audio); + cursor.anm2 = anm2_get(CURSOR_PATH, renderer, audio); + icons = anm2_get(ICONS_PATH, renderer, audio); + area.anm2 = anm2_get(AREA_PATH, renderer, audio); + if (area.anm2) { + Actor actor(area.anm2); + area.bounds = actor.null_rect_get(area.anm2->null_get(BOUNDS_NULL), -1, 0); + area.spawn = actor.null_rect_get(area.anm2->null_get(SPAWN_NULL), -1, 0); + area.spawn = is_rect_valid(area.spawn) ? area.spawn : area.bounds; + } + Json json; + for (const std::string& root : {roots.back(), folder}) + { + json.load(root + DATA_FILE); + if (!json.isValid) + continue; character_parse(json.root, *this, renderer, audio); sounds_parse(json.root, FILE_CHARACTER, *this, audio); } + interact_types_trim(*this); json.load(folder + "items.json"); if (json.isValid) { @@ -543,23 +551,17 @@ Actor Character::item_actor_get(int item) return actor; } -// The area a stage plays in: the last one a stage up to it names, or the first. -int Character::area_get(int stage) -{ - if (areas.empty()) - return -1; - int area = 0; - for (int i = 0; i < stage && i < (int)stages.size(); i++) - if (stages[i].area >= 0 && stages[i].area < (int)areas.size()) - area = stages[i].area; - return area; -} - std::string Character::save_path_get() { return pref_path_get("saves/" + id + ".txt"); } +// A stage's name, which its animations end with (past the first) and its dialogue pool is called. +std::string Character::stage_name_get(int stage) +{ + return STAGE_PREFIX + std::to_string(stage + 1); +} + // Every folder in resources/characters holding a character.json, in name order, with its preview loaded. std::vector characters_load(Renderer& renderer) { diff --git a/src/character.h b/src/character.h index 6706285..09393d8 100644 --- a/src/character.h +++ b/src/character.h @@ -5,8 +5,6 @@ #include #include -struct ImFont; - enum DataFile { FILE_CHARACTER, @@ -64,23 +62,23 @@ enum Hook HOOK_COUNT }; -struct Stage -{ - std::string animationSuffix; - std::string dialoguePool; - float weight = 0; - int area = -1; -}; +constexpr const char* ANM2_PATH = "gfx/character/character.anm2"; +constexpr const char* CURSOR_ANIMATION_CLICK = "Click"; +constexpr const char* CURSOR_ANIMATION_DISPOSE = "Dispose"; +constexpr const char* CURSOR_ANIMATION_GRAB = "Grab"; +constexpr const char* CURSOR_ANIMATION_HOVER = "Hover"; +constexpr const char* CURSOR_ANIMATION_IDLE = "Default"; +constexpr const char* CURSOR_ANIMATION_PAN = "Pan"; +constexpr const char* CURSOR_ANIMATION_ZOOM = "Zoom"; // A spot on the character the cursor acts on, starting from its type's settings. Types are kept the same way, for -// their tools and defaults. +// their buttons and defaults. Over capacity, the pool named with "Full" after it plays instead, if there is one. struct InteractArea { std::string animation; std::string cursorActive; std::string cursorHover; std::string dialoguePool; - std::string dialoguePoolFull; std::string icon; std::string idleOnEnd; std::string name; @@ -112,7 +110,7 @@ struct ExpandArea int null = -1; }; -// A background, with the world's bounds and where items spawn taken from its nulls. +// The background, with the world's bounds and where items spawn taken from its nulls. struct Area { Anm2* anm2 = nullptr; @@ -246,7 +244,6 @@ struct Traits float caloriesToKilogram = 0; float capacity = 0; float capacityMax = 0; - float capacityMin = 0; float digestionRate = 0; float digestionRateMax = 0; float digestionRateMin = 0; @@ -284,23 +281,10 @@ struct Swap struct Cursor { - std::string click; - std::string dispose; - std::string grab; - std::string hover; - std::string idle; - std::string pan; - std::string zoom; std::vector grabParticles; Anm2* anm2 = nullptr; }; -struct Menu -{ - ImFont* font = nullptr; - float rounding = 0; -}; - // The items a character can be given, their rarities, and how often they spawn. struct Catalog { @@ -316,35 +300,32 @@ struct Catalog int item_get(const std::string& id); }; -// Everything a character folder holds. The preview (what the select screen shows) loads at startup, the rest when -// played. +// Everything a character folder holds, over the defaults in common's character.json. The preview (what the select +// screen shows) loads at startup, the rest when played. Stages are the weights each one starts at. class Character { public: std::array sounds; - std::string anm2Path; std::string credits; std::string description; - std::string dialoguePool; std::string folder; std::string id; std::string key; std::string name; std::unordered_map anm2s; std::unordered_map particles; - std::vector areas; std::vector eatAreas; std::vector expandAreas; std::vector interactAreas; std::vector interactTypes; - std::vector stages; + std::vector stages; Roots roots; Alternate alternate; + Area area; Animations animations; Catalog catalog; Cursor cursor; Dialogue dialogue; - Menu menu; Swap blink; Swap talk; Traits traits; @@ -360,8 +341,8 @@ public: void load(Renderer& renderer, Audio& audio); Anm2* anm2_get(const std::string& path, Renderer& renderer, Audio& audio); Actor item_actor_get(int item); - int area_get(int stage); std::string save_path_get(); + std::string stage_name_get(int stage); }; std::vector characters_load(Renderer& renderer); diff --git a/src/interact.cpp b/src/interact.cpp index 41e4cc3..2cca2d6 100644 --- a/src/interact.cpp +++ b/src/interact.cpp @@ -1,5 +1,7 @@ #include "play.h" +constexpr const char* FULL_SUFFIX = "Full"; + constexpr float AUTO_ACCELERATION = 0.45f; constexpr float AUTO_ANGLE_STEP = 0.08f; constexpr float AUTO_BRAKING = 2; @@ -64,8 +66,9 @@ void Interactions::update(Play& play, Pointer pointer) bool isReady = cooldowns[i] <= 0; cooldowns[i] = std::max(0, entry.dialogueCooldown); Dialogue& dialogue = character.dialogue; - bool isFull = body.is_over_capacity() && dialogue.pools.contains(entry.dialoguePoolFull); - const std::string& pool = isFull ? entry.dialoguePoolFull : entry.dialoguePool; + std::string pool = entry.dialoguePool; + if (body.is_over_capacity() && dialogue.pools.contains(pool + FULL_SUFFIX)) + pool += FULL_SUFFIX; if (dialogue.pools.contains(pool)) { if (text.is_interruptible() && isReady) @@ -92,7 +95,7 @@ void Interactions::update(Play& play, Pointer pointer) } if ((wasInteracting && !isInteracting) || (wasHovering && !isHovering)) - active.play(character.cursor.idle); + active.play(CURSOR_ANIMATION_IDLE); if (wasHolding && !isHolding && !body.actor.pending) body.idle_play(); pointerPrevious = pointer.position; diff --git a/src/items.cpp b/src/items.cpp index 0f14605..95399d2 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -87,8 +87,7 @@ void Items::update(Play& play, Pointer pointer) Stats& stats = body.stats; Actor& active = play.pointer_actor_get(); Audio& audio = *play.audio; - int areaIndex = character.area_get(body.stage); - Area area = areaIndex >= 0 ? character.areas[areaIndex] : Area{}; + Area& area = character.area; Rect bounds = play.bounds; bool isOver = body.is_over_capacity(); auto text_play = [&](Hook hook) { @@ -97,7 +96,7 @@ void Items::update(Play& play, Pointer pointer) }; auto release = [&](bool isThrown) { drop(); - active.play(character.cursor.idle); + active.play(CURSOR_ANIMATION_IDLE); if (!isThrown && !body.actor.pending) body.idle_play(); }; @@ -122,7 +121,7 @@ void Items::update(Play& play, Pointer pointer) spawnDelay--; for (int id : spawnQueue) { - Rect spawn = areaIndex >= 0 ? area.spawn : bounds; + Rect spawn = area.anm2 ? area.spawn : bounds; Vec2 position = {spawn.x + random_get(0, spawn.w), spawn.y + random_get(0, spawn.h)}; if (!discovered[id]) { @@ -172,7 +171,7 @@ void Items::update(Play& play, Pointer pointer) stats.calories += item.calories; stats.totalCalories += item.calories; stats.capacity = - std::clamp(stats.capacity + item.capacityBonus, traits.capacityMin, traits.capacityMax); + std::clamp(stats.capacity + item.capacityBonus, traits.capacity, traits.capacityMax); stats.digestionRate = std::clamp(stats.digestionRate + item.digestionBonus, traits.digestionRateMin, traits.digestionRateMax); stats.totalConsumed++; @@ -244,7 +243,7 @@ void Items::update(Play& play, Pointer pointer) { isHovered = true; tooltip = held == i || pointer.isAutomated ? -1 : entity.id; - active.play(character.cursor.hover, TRANSITION_IF_IDLE); + active.play(CURSOR_ANIMATION_HOVER, TRANSITION_IF_IDLE); play.cursorState = CURSOR_HOVER; if (pointer.isClicked) { @@ -254,14 +253,14 @@ void Items::update(Play& play, Pointer pointer) } if (pointer.isDown) { - active.play(character.cursor.grab); + active.play(CURSOR_ANIMATION_GRAB); play.cursorState = CURSOR_ACTION; held = moved = i; } if (pointer.isRightClicked) { audio.play(character.sounds[SOUND_DISPOSE]); - active.play(character.cursor.dispose, TRANSITION_FORCE, INTERRUPT_NEVER); + active.play(CURSOR_ANIMATION_DISPOSE, TRANSITION_FORCE, INTERRUPT_NEVER); play.particles_spawn(character.catalog.disposeParticles, position); held = held == i ? -1 : held > i ? held - 1 : held; moved = moved == i ? -1 : moved > i ? moved - 1 : moved; @@ -313,6 +312,6 @@ void Items::update(Play& play, Pointer pointer) held = (int)list.size() - 1; } if (wasHovered && !isHovered) - active.play(character.cursor.idle); + active.play(CURSOR_ANIMATION_IDLE); pointerPrevious = pointer.position; } diff --git a/src/main.cpp b/src/main.cpp index b1b5171..c0e97f1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,6 @@ struct App Renderer renderer; Settings settings; Ui ui; - ImFont* font = nullptr; SDL_GLContext context = nullptr; SDL_Window* window = nullptr; Uint64 tickPrevious = 0; @@ -65,7 +64,6 @@ static void play_exit() return; app.play.save(); app.settings.lastCharacter.clear(); - ImGui::GetIO().FontDefault = app.font; style_set(app.settings.color); app.screen = SCREEN_SELECT; } @@ -80,8 +78,7 @@ static void play_start(const std::string& key, Game game) app.ui.characterIndex = index; app.play.set(character, game, app.renderer, app.audio); app.settings.lastCharacter = character.key; - ImGui::GetIO().FontDefault = character.menu.font ? character.menu.font : app.font; - style_set(app.settings.isCharacterColor ? character.color : app.settings.color, character.menu.rounding); + style_set(app.settings.isCharacterColor ? character.color : app.settings.color); app.screen = SCREEN_PLAY; } @@ -133,8 +130,7 @@ static void init() ImGui::StyleColorsDark(); ImGui_ImplSDL3_InitForOpenGL(app.window, app.context); ImGui_ImplOpenGL3_Init(GLSL_VERSION); - app.font = io.Fonts->AddFontFromFileTTF(path_get("font/font.ttf").c_str(), FONT_NORMAL); - io.FontDefault = app.font; + io.FontDefault = io.Fonts->AddFontFromFileTTF(path_get("font/font.ttf").c_str(), FONT_NORMAL); style_set(app.settings.color); app.ui.audio = &app.audio; diff --git a/src/play.cpp b/src/play.cpp index 9ae4f77..8434ba2 100644 --- a/src/play.cpp +++ b/src/play.cpp @@ -115,10 +115,10 @@ void Play::save() // Shows the area of the body's stage, with its bounds. void Play::area_set() { - int index = character->area_get(body.stage); - if (index >= 0 && area.anm2 != character->areas[index].anm2) - area = Actor(character->areas[index].anm2); - bounds = index >= 0 && is_rect_valid(character->areas[index].bounds) ? character->areas[index].bounds : WORLD_BOUNDS; + Area& world = character->area; + if (world.anm2 && area.anm2 != world.anm2) + area = Actor(world.anm2); + bounds = is_rect_valid(world.bounds) ? world.bounds : WORLD_BOUNDS; } void Play::toast_add(const std::string& message) @@ -233,7 +233,7 @@ void Play::update(Vec2 screenSize) if (cursor.anm2 && !autofeed.isActive && pointer.isClicked && !items.isHovered && !interactions.isHovering && cursorState == CURSOR_DEFAULT) - cursor.play(character->cursor.click, TRANSITION_FORCE, INTERRUPT_ALWAYS); + cursor.play(CURSOR_ANIMATION_CLICK, TRANSITION_FORCE, INTERRUPT_ALWAYS); body.update(events); events_handle(screenSize); @@ -250,7 +250,7 @@ void Play::update(Vec2 screenSize) { bool isActive = (each == &autoCursor) == autofeed.isActive; if ((!isActive || cursorState == CURSOR_DEFAULT) && !each->pending) - each->play(character->cursor.idle, TRANSITION_IF_IDLE); + each->play(CURSOR_ANIMATION_IDLE, TRANSITION_IF_IDLE); each->update(*audio); } cursorState = CURSOR_DEFAULT; @@ -323,14 +323,14 @@ void Camera::update(Play& play, Vec2 screenSize) if (ImGui::IsMouseDown(ImGuiMouseButton_Middle) || (ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGui::IsKeyDown(ImGuiMod_Ctrl))) { - play.cursor.play(cursor.pan); + play.cursor.play(CURSOR_ANIMATION_PAN); pan = pan - Vec2{io.MouseDelta.x, io.MouseDelta.y} / (zoom / PERCENT); } if (io.MouseWheel != 0) { Vec2 mouse = {io.MousePos.x, io.MousePos.y}; Vec2 world = world_get(mouse); - play.cursor.play(cursor.zoom); + play.cursor.play(CURSOR_ANIMATION_ZOOM); int index = std::clamp(zoom_index_get(zoom) + (io.MouseWheel > 0 ? 1 : -1), 0, (int)std::size(ZOOM_LADDER) - 1); zoom = ZOOM_LADDER[index]; diff --git a/src/ui.cpp b/src/ui.cpp index a341fd6..e05b1d7 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -24,6 +24,7 @@ constexpr float INDICATOR_BOB = 0.12f; constexpr float INDICATOR_SIZE = 0.35f; constexpr float INDICATOR_SPEED = 2; constexpr float ITEM_SIZE = 96; +constexpr float ROUNDING = 10; constexpr float BAR_WIDTH_MIN = 120; constexpr float BUTTON_SIZE_MAX = 96; constexpr float BUTTON_SIZE_MIN = 56; @@ -108,10 +109,10 @@ static Palette palette_get(Color color) {ImGuiCol_ChildBg, shade(COLOR_BACKGROUND, 0)}}}; } -void style_set(Color color, float rounding) +void style_set(Color color) { ImGuiStyle& style = ImGui::GetStyle(); - style.WindowRounding = style.FrameRounding = style.GrabRounding = style.PopupRounding = rounding; + style.WindowRounding = style.FrameRounding = style.GrabRounding = style.PopupRounding = ROUNDING; style.ScrollbarSize = SCROLLBAR_SIZE; style.FontSizeBase = FONT_NORMAL; for (auto [id, value] : palette_get(color)) @@ -461,9 +462,7 @@ void Ui::taskbar_draw(Play& play, ImVec2 screen) Items& items = play.items; Dialogue& dialogue = data.dialogue; ImGuiStyle& style = ImGui::GetStyle(); - std::string moodPool = body.stage <= 0 - ? data.dialoguePool - : data.stages[std::min(body.stage, (int)data.stages.size()) - 1].dialoguePool; + std::string moodPool = data.stage_name_get(body.stage); const char* labels[] = {"Weight", "Capacity", "Digestion"}; bool isMax = body.stage >= (int)data.stages.size(); @@ -777,8 +776,7 @@ void Ui::settings_draw(Settings& settings) isColorChanged = true; } if (isColorChanged) - style_set(character && settings.isCharacterColor ? character->color : settings.color, - character ? character->menu.rounding : 0); + style_set(character && settings.isCharacterColor ? character->color : settings.color); if (!character) return; ImGui::Separator(); @@ -837,7 +835,7 @@ void Ui::cheats_draw(Play& play) Stats& stats = body.stats; Traits& traits = data.traits; is_fx(ImGui::SliderFloat("Calories", &stats.calories, 0, body.capacity_max_get(), "%0.0f kcal")); - is_fx(ImGui::SliderFloat("Capacity", &stats.capacity, traits.capacityMin, traits.capacityMax, "%0.0f kcal")); + is_fx(ImGui::SliderFloat("Capacity", &stats.capacity, traits.capacity, traits.capacityMax, "%0.0f kcal")); if (is_fx(ImGui::SliderFloat("Weight", &stats.weight, traits.weight, traits.weightMax, "%0.2f kg"))) { body.stage_set(body.stage_get(stats.weight)); @@ -1043,7 +1041,7 @@ void Ui::select_update(std::vector& characters, Settings& settings) } if (is_fx(ImGui::BeginTabItem("In Game"))) { - Anm2* anm2 = selected->anm2_get(selected->anm2Path, *renderer, *audio); + Anm2* anm2 = selected->anm2_get(ANM2_PATH, *renderer, *audio); if (previewActor.anm2 != anm2 || !anm2) previewActor = Actor(anm2); previewActor.update(*audio); diff --git a/src/ui.h b/src/ui.h index 1683440..a42d396 100644 --- a/src/ui.h +++ b/src/ui.h @@ -52,7 +52,7 @@ struct Command using Commands = std::vector; -void style_set(Color color, float rounding = 0); +void style_set(Color color); // The taskbar, its windows, the dialogue box, toasts and the select screen. class Ui