diff --git a/.gitea/ISSUE_TEMPLATE/choreo.yaml b/.gitea/ISSUE_TEMPLATE/choreo.yaml new file mode 100644 index 0000000..1000bc1 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/choreo.yaml @@ -0,0 +1,21 @@ +name: Choreography Problem +about: A template for discussing choreography problems +title: "[Chapter XX]: " +labels: +- bug +- "help wanted" +body: + - type: input + id: where + attributes: + label: On what line did this bug occour? + placeholder: "I \"This shit fucks and sucks!\"" + validations: + required: true + - type: textarea + id: what + attributes: + label: "What happened?" + placeholder: "The expression \"Inco Sad\" was used at a time when it shouldn't have been used." + validations: + required: true diff --git a/.woodpecker.yml b/.woodpecker.yml index 12ec81a..63af922 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -30,14 +30,14 @@ steps: - event: cron build: - image: openjdk:8 + image: openjdk:21-jdk-bookworm commands: - apt update - - apt install libgl1 -y + - apt install libgl1 patch -y - sed -i -e "s/VERSION/${RenpyVersion}/g" game/options.rpy #Change the renpy version to the woodpecker reuqested one. #Get Renkit - - wget -qO- "https://github.com/kobaltcore/renkit/releases/download/v${RenkitVersion}/renkit-linux-amd64.tar.gz" | tar xz --directory=/tmp/ - - /tmp/renconstruct build -i "." -c "renconstruct.toml" -o dist/ + - wget -qO- "https://github.com/kobaltcore/renkit/releases/download/v${RenkitVersion}/renkit-x86_64-unknown-linux-gnu.tar.xz" | tar -Jax --directory=/tmp/ + - /tmp/renkit-x86_64-unknown-linux-gnu/renconstruct build "." dist/ - mkdir "dist/android" - mv dist/*.apk "dist/android" - cd /tmp/ @@ -83,6 +83,6 @@ steps: matrix: RenpyVersion: - - "8.1.3" + - "8.2.1" RenkitVersion: - - "3.3.1" + - "4.1.0" diff --git a/LICENSE b/LICENSE index 51238cd..b953b3b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,385 +1,385 @@ -The "GNU AFFERO GENERAL PUBLIC LICENSE" as wrote out in the "GNU AFFERO GENERAL PUBLIC LICENSE V3.txt" file applies to all computer software files, source or object code, originating from Cavemanon or its contributors. -The "Cavemanon Affero GPL v3.0 Video Game Store Front Exception v2.0" as wrote out in the "GPL-Exception.txt" file applies to all copyrightable assets originating from Cavemanon or its contributors that come with this software. -The "Attribution-ShareAlike 4.0 International" as wrote out in the "CC-BY-SA-4.0.txt" file applies to all copyrightable audio and image assets originating from Cavemanon or its contributors that is not a computer software file, with the exception of the following assets, which use "Attribution 4.0 International" as wrote out in the "CC-BY-4.0.txt": - -game\images\animations\notagoodtime\anim_notagoodtime_f1.png - -game\images\animations\notagoodtime\anim_notagoodtime_f2.png - -game\images\animations\toilet\anim_toilet.webm - -game\images\animations\toilet\anim_toilet_mask.webm - -game\images\credits\spr_creditsimage_notagoodtime.png - -game\images\credits\spr_creditsimage_miahoe.png - -Non-Cavemanon sourced items with attribution clauses -==================================================== - ---- SOUNDS --- - -"S Dissapproval - Alt.wav" by Processaurus -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/Processaurus/sounds/440088/ - -"Rolling Creak.wav" by lukaspearse -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/lukaspearse/sounds/149736/ - -"Interior Wind Noise_Zoom H6.wav" by omnisounddesign -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://freesound.org/people/omnisounddesign/sounds/335703/ - -"Take off - Small pop.wav" by Quistard -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://freesound.org/people/Quistard/sounds/237753/ - -"Jet_whoosh.wav" by Benboncan -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/Benboncan/sounds/167563/ - -"Dodgeball_Bounce1.wav" by burnsie289 -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://freesound.org/people/burnsie289/sounds/152414/ - -"Cracking knuckles.flac" by CGEffex -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/CGEffex/sounds/93981/ - -"MassiveBlast.wav" by daveincamas -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/daveincamas/sounds/58507/ - -"Water Swirl, Small, 17.wav" by InspectorJ -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/InspectorJ/sounds/398708/ - -"Splash, Jumping, A" by InspectorJ -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/InspectorJ/sounds/352101/ - -"etl Window 24-96.wav" by cmusounddesign -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://freesound.org/people/cmusounddesign/sounds/95893/ - -"elevator 3.wav" by Trautwein -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/Trautwein/sounds/262574/ - -"taking the elevator" by Tomlija -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://freesound.org/people/Tomlija/sounds/202985/ - -"Snapping, Wodden Fence, L.wav" by InspectorJ -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/InspectorJ/sounds/352196/ - -"Elastic Band on Tarpaulin" by RICHERlandTV -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/RICHERlandTV/sounds/592262/ - -"Whip Crack 01.wav" by CGEffex -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/CGEffex/sounds/93100/ - -"Snow Ball Throw And Splat" by Mike Koenig -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://soundbible.com/632-Snow-Ball-Throw-And-Splat.html - -"Power up yells" by AmeAngelofSin -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/AmeAngelofSin/sounds/382010/ - -"female dying scream" by AmeAngelofSin -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/AmeAngelofSin/sounds/394610/ - -"dying female" by AmeAngelofSin -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/AmeAngelofSin/sounds/345049/ - -"Female battle cries/grunts" by AmeAngelofSin -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/AmeAngelofSin/sounds/264982/ - -"ANGRY DOG BARK SNARL flat.wav" by AmeAngelofSin -LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) -https://freesound.org/people/deleted_user_3424813/sounds/260776/ - -"WIT8 - pain" by phantastonia -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/phantastonia/sounds/615023/ - -"Door open and close, close" by Moulaythami -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/Moulaythami/sounds/555095/ - -"mitchellsounds - EXT_spring_light_wind_mourningdove_MS.wav" -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/mitchellsounds/sounds/517941/ - -"20170618_city.garden.fountain.wav" -LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) -https://freesound.org/people/dobroide/sounds/396277/ - ---- MUSIC --- - -mus_angry.ogg - URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/HYPER_METAL_/Loyalty_Freak_Music_-_HYPER_METAL__-_06_SUPER_METAL/ - author: Loyalty Freak Music - album: HYPER METAL ! - songname: SUPER METAL - license: CC0 - -mus_arcade1.ogg - URL: https://sampleswap.org/mp3/song.php?id=1185 - author: benzoul - songname: lovepartyinjapan - license: CC-BY - -mus_arcade2.ogg - URL: https://sampleswap.org/mp3/song.php?id=413 - author: emanon - songname: 21seiki Techno Shonen - license: CC-BY - -mus_awkward.ogg - URL: https://freemusicarchive.org/music/Soft_and_Furious/The_Merfolk_I_Should_Turn_To_Be/Soft_and_Furious_-_The_Merfolk_I_Should_Turn_To_Be_-_08_Hyper_Staying/ - author: Soft and Furious - album: The Merfolk I Should Turn To Be - songname: Hyper Staying - license: CC0 - -mus_ben.ogg - URL: https://freemusicarchive.org/music/koi-discovery/post-morphose/icar-fellmp3/ - author: Koi-discovery - album: Post-Morphose - songname: Icar_Fell.mp3 - license: CC0 - -mus_calm2.ogg - URL: https://freemusicarchive.org/music/Komiku/Its_time_for_adventure__vol_2/Komiku_-_Its_time_for_adventure_vol_2_-_08_Dreaming_of_you/ - author: Komiku - album: It's time for adventure ! vol 2 - songname: Dreaming of you - license: CC0 - -mus_calm.ogg - URL: https://freemusicarchive.org/music/Soft_and_Furious/The_Merfolk_I_Should_Turn_To_Be/Soft_and_Furious_-_The_Merfolk_I_Should_Turn_To_Be_-_02_Youre_no_good_but_I_love_you/ - author: Soft and Furious - album: The Merfolk I Should Turn To Be - songname: You're no good but I love you - license: CC0 - -mus_casual.ogg - URL: https://freemusicarchive.org/music/holiznacc0/left-overs/even-after-all-these-years/ - author: HoliznaCC0 - album: Left Overs - songname: Even After All These Years - license: CC0 - -mus_chill1.ogg - URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/INSTRUMENTAL_RB_BEATS_TO_SING_OR_RAP_ON/Loyalty_Freak_Music_-_INSTRUMENTAL_RB_BEATS_TO_SING_OR_RAP_ON_-_08_I_care - author: Loyalty Freak Music - album: INSTRUMENTAL R&B BEATS TO SING OR RAP ON - songname: I care - license: CC0 - -mus_chill2.ogg - URL: https://freemusicarchive.org/music/holiznacc0/busted-guitar-jazz/4-jazz/ - author: HoliznaCC0 - album: Busted Guitar (JAZZ) - songname: 4 (jazz) - license: CC0 - -mus_chill3.ogg - URL: https://freemusicarchive.org/music/holiznacc0/lo-fi-and-chill/everything-you-ever-dreamed/ - author: HoliznaCC0 - album: Lo-fi And Chill - songname: Everything You Ever Dreamed. - license: CC0 - -mus_chill_thinking.ogg - URL: https://freemusicarchive.org/music/holiznacc0/sad-beats/chills/ - author: HoliznaCC0 - album: Sad Beats - songname: Chills - license: CC0 - -mus_damien.ogg - URL: https://freemusicarchive.org/music/holiznacc0/rock-montage/americana-1/ - author: HoliznaCC0 - album: Rock Montage - songname: Americana 1 - license: CC0 - -mus_general.ogg - URL: https://freemusicarchive.org/music/wax-lyricist/the-wax-lyricist/high-on-loungin/ - author: Wax Lyricist - album: The Wax Lyricist - songname: High on Loungin' - license: CC0 - -mus_happy2.ogg - URL: https://freemusicarchive.org/music/holiznacc0/power-pop/a-small-town-on-pluto-1/ - author: HoliznaCC0 - album: Power Pop! - songname: A Small Town On Pluto - license: CC0 - -mus_iadakan.ogg - URL: https://freemusicarchive.org/music/holiznacc0/left-overs/space-3/ - author: HoliznaCC0 - album: Left Overs - songname: Space! - license: CC0 - -mus_inco.ogg - URL: https://freemusicarchive.org/music/koi-discovery/cyborg-breakdown/an-ocytocin-problemmp3/ - author: Koi-discovery - album: Cyborg Breakdown - songname: An_Ocytocin_Problem.mp3 - license: CC0 - -mus_introspective3 - URL: https://freemusicarchive.org/music/Monplaisir/Space_Porn/Monplaisir_-_Space_Porn_-_04_Protect/ - author: Monplaisir - album: Space Porn - songname: Protect - license: CC0 - -mus_melancholy2.ogg - URL: https://freemusicarchive.org/music/Monplaisir/Space_Porn/Monplaisir_-_Space_Porn_-_08_Stargazer/ - author: Monplaisir - album: Space Porn - songname: Stargazer - license: CC0 - -mus_melancholy_introspective.ogg - URL: https://freemusicarchive.org/music/Monplaisir/PIGEONS_ARE_THE_BEST/Monplaisir_-_PIGEONS_ARE_THE_BEST_-_05_Lightbull/ - author: Monplaisir - album: PIGEONS ARE THE BEST - songname: Lightbull - license: CC0 - -mus_melancholy.ogg - URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/TO_CHILL_AND_STAY_AWAKE/Loyalty_Freak_Music_-_TO_CHILL_AND_STAY_AWAKE_-_06_People_are_spinning/ - author: Loyalty Freak Music - album: TO CHILL AND STAY AWAKE - songname: People are spinning - license: CC0 - -mus_melancholy_olivia.ogg - URL: https://freemusicarchive.org/music/Monplaisir/Pretty_and_Invisible/Monplaisir_-_Pretty_and_Invisible_-_02_Far_and_High/ - author: Monplaisir - album: Pretty and Invisible - songname: Far and High - license: CC0 - -mus_mia1.ogg - URL: https://freemusicarchive.org/music/holiznacc0/rock-montage/punk/ - author: HoliznaCC0 - album: Rock Montage - songname: Punk - license: CC0 - -mus_mia2.ogg - URL: https://freemusicarchive.org/music/holiznacc0/rock-montage/grunge/ - author: HoliznaCC0 - album: Rock Montage - songname: Grunge - license: CC0 -mus_misc.ogg - URL: https://freemusicarchive.org/music/holiznacc0/kick-it-laid-back-hiphop/unwind/ - author: HoliznaCC0 - album: Kick It (laid back HipHop) - songname: Unwind - license: CC0 - -mus_pissed.ogg - URL: https://freemusicarchive.org/music/Monplaisir/Pretty_and_Invisible/Monplaisir_-_Pretty_and_Invisible_-_06_Talentuous_and_Shy/ - author: Monplaisir - album: Pretty and Invisible - songname: Talentuous and Shy - license: CC0 - -mus_rock.ogg - URL: https://freesound.org/people/Jibey-/sounds/572947/ - author: Jibey- - songname: Rock music.wav - license: CC-BY - -mus_slow.ogg - URL: https://freemusicarchive.org/music/holiznacc0/lo-fi-and-chill/ramenmp3/ - author: Ramen.mp3 - album: Lo-fi And Chill - songname: ramenmp3 - license: CC0 - -mus_thoughts1.ogg - URL: https://freemusicarchive.org/music/Monplaisir/PIGEONS_ARE_THE_BEST/Monplaisir_-_PIGEONS_ARE_THE_BEST_-_01_Jump__Oh_Hi_Mark/ - author: Monplaisir - album: PIGEONS ARE THE BEST - songname: Jump ! Oh Hi Mark - license: CC0 - -mus_thoughts2 - URL: https://freemusicarchive.org/music/Monplaisir/Pretty_and_Invisible/Monplaisir_-_Pretty_and_Invisible_-_01_Loved_and_Respected/ - author: Monplaisir - album: Pretty and Invisible - songname: Loved and Respected - license: CC0 - -mus_titlescreen.ogg - URL: https://freemusicarchive.org/music/holiznacc0/be-happy-with-who-you-are/finding-yourself/ - author: HoliznaCC0 - album: Be Happy With Who You Are - songname: Finding Yourself - license: CC0 - -mus_trouble.ogg - URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/TO_CHILL_AND_STAY_AWAKE/Loyalty_Freak_Music_-_TO_CHILL_AND_STAY_AWAKE_-_05_Traveling_in_your_mind/ - author: Loyalty Freak Music - album: TO CHILL AND STAY AWAKE - songname: Traveling in your mind - license: CC0 - -mus_upbeat.ogg - URL: https://freemusicarchive.org/music/holiznacc0/lo-fi-and-chill/busted-jazz - author: HoliznaCC0 - album: Lo-fi And Chill - songname: Busted Jazz - license: CC0 - ---- GRAPHICS --- - -"The Resort at Pelican Hill" by Ansel Adams -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) -https://www.flickr.com/photos/prayitnophotography/8468342892/ - -"Inside 360 Restaurant" by Larry Koester -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) -https://www.flickr.com/photos/larrywkoester/22976555815/ - -"Banquet set up - Novotel Century Hong Kong Hotel" by RichardToHKG -LICENSED UNDER ATTRIBUTION 3.0 UNPORTED (CC BY 3.0) -https://commons.wikimedia.org/wiki/File:Banquet_set_up_-_Novotel_Century_Hong_Kong_Hotel.jpg - -"Seattle from the Space Needle" by Peter Kaminski -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) -https://www.flickr.com/photos/peterkaminski/5444827/ - -"Bar" by fr1da -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) -https://www.flickr.com/photos/denero188_36624278/2090013005 - -"Pleading face" and "Crocodile emoji" by Twitter/X -Copyright 2020 Twitter, Inc and other contributors -Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ - -"Swing" by Julia Koefender -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) -https://www.flickr.com/photos/juliakoefender/7128081493/ - -"Kitchen in our Hollywood Hills Hidden Gem apartment - Los Angeles, California, USA" by Glen Bowman -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) -https://www.flickr.com/photos/glenbowman/18180131123/ - -"Sunset with yachts near Phuket island, Thailand XOKA2050s" by Phuket@photographer.net -LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +The "GNU AFFERO GENERAL PUBLIC LICENSE" as wrote out in the "GNU AFFERO GENERAL PUBLIC LICENSE V3.txt" file applies to all computer software files, source or object code, originating from Cavemanon or its contributors. +The "Cavemanon Affero GPL v3.0 Video Game Store Front Exception v2.0" as wrote out in the "GPL-Exception.txt" file applies to all copyrightable assets originating from Cavemanon or its contributors that come with this software. +The "Attribution-ShareAlike 4.0 International" as wrote out in the "CC-BY-SA-4.0.txt" file applies to all copyrightable audio and image assets originating from Cavemanon or its contributors that is not a computer software file, with the exception of the following assets, which use "Attribution 4.0 International" as wrote out in the "CC-BY-4.0.txt": + -game\images\animations\notagoodtime\anim_notagoodtime_f1.png + -game\images\animations\notagoodtime\anim_notagoodtime_f2.png + -game\images\animations\toilet\anim_toilet.webm + -game\images\animations\toilet\anim_toilet_mask.webm + -game\images\credits\spr_creditsimage_notagoodtime.png + -game\images\credits\spr_creditsimage_miahoe.png + +Non-Cavemanon sourced items with attribution clauses +==================================================== + +--- SOUNDS --- + +"S Dissapproval - Alt.wav" by Processaurus +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/Processaurus/sounds/440088/ + +"Rolling Creak.wav" by lukaspearse +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/lukaspearse/sounds/149736/ + +"Interior Wind Noise_Zoom H6.wav" by omnisounddesign +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://freesound.org/people/omnisounddesign/sounds/335703/ + +"Take off - Small pop.wav" by Quistard +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://freesound.org/people/Quistard/sounds/237753/ + +"Jet_whoosh.wav" by Benboncan +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/Benboncan/sounds/167563/ + +"Dodgeball_Bounce1.wav" by burnsie289 +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://freesound.org/people/burnsie289/sounds/152414/ + +"Cracking knuckles.flac" by CGEffex +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/CGEffex/sounds/93981/ + +"MassiveBlast.wav" by daveincamas +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/daveincamas/sounds/58507/ + +"Water Swirl, Small, 17.wav" by InspectorJ +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/InspectorJ/sounds/398708/ + +"Splash, Jumping, A" by InspectorJ +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/InspectorJ/sounds/352101/ + +"etl Window 24-96.wav" by cmusounddesign +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://freesound.org/people/cmusounddesign/sounds/95893/ + +"elevator 3.wav" by Trautwein +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/Trautwein/sounds/262574/ + +"taking the elevator" by Tomlija +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://freesound.org/people/Tomlija/sounds/202985/ + +"Snapping, Wodden Fence, L.wav" by InspectorJ +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/InspectorJ/sounds/352196/ + +"Elastic Band on Tarpaulin" by RICHERlandTV +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/RICHERlandTV/sounds/592262/ + +"Whip Crack 01.wav" by CGEffex +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/CGEffex/sounds/93100/ + +"Snow Ball Throw And Splat" by Mike Koenig +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://soundbible.com/632-Snow-Ball-Throw-And-Splat.html + +"Power up yells" by AmeAngelofSin +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/AmeAngelofSin/sounds/382010/ + +"female dying scream" by AmeAngelofSin +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/AmeAngelofSin/sounds/394610/ + +"dying female" by AmeAngelofSin +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/AmeAngelofSin/sounds/345049/ + +"Female battle cries/grunts" by AmeAngelofSin +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/AmeAngelofSin/sounds/264982/ + +"ANGRY DOG BARK SNARL flat.wav" by AmeAngelofSin +LICENSED UNDER ATTRIBUTION 3.0 (CC BY 3.0) +https://freesound.org/people/deleted_user_3424813/sounds/260776/ + +"WIT8 - pain" by phantastonia +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/phantastonia/sounds/615023/ + +"Door open and close, close" by Moulaythami +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/Moulaythami/sounds/555095/ + +"mitchellsounds - EXT_spring_light_wind_mourningdove_MS.wav" +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/mitchellsounds/sounds/517941/ + +"20170618_city.garden.fountain.wav" +LICENSED UNDER ATTRIBUTION 4.0 (CC BY 4.0) +https://freesound.org/people/dobroide/sounds/396277/ + +--- MUSIC --- + +mus_angry.ogg + URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/HYPER_METAL_/Loyalty_Freak_Music_-_HYPER_METAL__-_06_SUPER_METAL/ + author: Loyalty Freak Music + album: HYPER METAL ! + songname: SUPER METAL + license: CC0 + +mus_arcade1.ogg + URL: https://sampleswap.org/mp3/song.php?id=1185 + author: benzoul + songname: lovepartyinjapan + license: CC-BY + +mus_arcade2.ogg + URL: https://sampleswap.org/mp3/song.php?id=413 + author: emanon + songname: 21seiki Techno Shonen + license: CC-BY + +mus_awkward.ogg + URL: https://freemusicarchive.org/music/Soft_and_Furious/The_Merfolk_I_Should_Turn_To_Be/Soft_and_Furious_-_The_Merfolk_I_Should_Turn_To_Be_-_08_Hyper_Staying/ + author: Soft and Furious + album: The Merfolk I Should Turn To Be + songname: Hyper Staying + license: CC0 + +mus_ben.ogg + URL: https://freemusicarchive.org/music/koi-discovery/post-morphose/icar-fellmp3/ + author: Koi-discovery + album: Post-Morphose + songname: Icar_Fell.mp3 + license: CC0 + +mus_calm2.ogg + URL: https://freemusicarchive.org/music/Komiku/Its_time_for_adventure__vol_2/Komiku_-_Its_time_for_adventure_vol_2_-_08_Dreaming_of_you/ + author: Komiku + album: It's time for adventure ! vol 2 + songname: Dreaming of you + license: CC0 + +mus_calm.ogg + URL: https://freemusicarchive.org/music/Soft_and_Furious/The_Merfolk_I_Should_Turn_To_Be/Soft_and_Furious_-_The_Merfolk_I_Should_Turn_To_Be_-_02_Youre_no_good_but_I_love_you/ + author: Soft and Furious + album: The Merfolk I Should Turn To Be + songname: You're no good but I love you + license: CC0 + +mus_casual.ogg + URL: https://freemusicarchive.org/music/holiznacc0/left-overs/even-after-all-these-years/ + author: HoliznaCC0 + album: Left Overs + songname: Even After All These Years + license: CC0 + +mus_chill1.ogg + URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/INSTRUMENTAL_RB_BEATS_TO_SING_OR_RAP_ON/Loyalty_Freak_Music_-_INSTRUMENTAL_RB_BEATS_TO_SING_OR_RAP_ON_-_08_I_care + author: Loyalty Freak Music + album: INSTRUMENTAL R&B BEATS TO SING OR RAP ON + songname: I care + license: CC0 + +mus_chill2.ogg + URL: https://freemusicarchive.org/music/holiznacc0/busted-guitar-jazz/4-jazz/ + author: HoliznaCC0 + album: Busted Guitar (JAZZ) + songname: 4 (jazz) + license: CC0 + +mus_chill3.ogg + URL: https://freemusicarchive.org/music/holiznacc0/lo-fi-and-chill/everything-you-ever-dreamed/ + author: HoliznaCC0 + album: Lo-fi And Chill + songname: Everything You Ever Dreamed. + license: CC0 + +mus_chill_thinking.ogg + URL: https://freemusicarchive.org/music/holiznacc0/sad-beats/chills/ + author: HoliznaCC0 + album: Sad Beats + songname: Chills + license: CC0 + +mus_damien.ogg + URL: https://freemusicarchive.org/music/holiznacc0/rock-montage/americana-1/ + author: HoliznaCC0 + album: Rock Montage + songname: Americana 1 + license: CC0 + +mus_general.ogg + URL: https://freemusicarchive.org/music/wax-lyricist/the-wax-lyricist/high-on-loungin/ + author: Wax Lyricist + album: The Wax Lyricist + songname: High on Loungin' + license: CC0 + +mus_happy2.ogg + URL: https://freemusicarchive.org/music/holiznacc0/power-pop/a-small-town-on-pluto-1/ + author: HoliznaCC0 + album: Power Pop! + songname: A Small Town On Pluto + license: CC0 + +mus_iadakan.ogg + URL: https://freemusicarchive.org/music/holiznacc0/left-overs/space-3/ + author: HoliznaCC0 + album: Left Overs + songname: Space! + license: CC0 + +mus_inco.ogg + URL: https://freemusicarchive.org/music/koi-discovery/cyborg-breakdown/an-ocytocin-problemmp3/ + author: Koi-discovery + album: Cyborg Breakdown + songname: An_Ocytocin_Problem.mp3 + license: CC0 + +mus_introspective3 + URL: https://freemusicarchive.org/music/Monplaisir/Space_Porn/Monplaisir_-_Space_Porn_-_04_Protect/ + author: Monplaisir + album: Space Porn + songname: Protect + license: CC0 + +mus_melancholy2.ogg + URL: https://freemusicarchive.org/music/Monplaisir/Space_Porn/Monplaisir_-_Space_Porn_-_08_Stargazer/ + author: Monplaisir + album: Space Porn + songname: Stargazer + license: CC0 + +mus_melancholy_introspective.ogg + URL: https://freemusicarchive.org/music/Monplaisir/PIGEONS_ARE_THE_BEST/Monplaisir_-_PIGEONS_ARE_THE_BEST_-_05_Lightbull/ + author: Monplaisir + album: PIGEONS ARE THE BEST + songname: Lightbull + license: CC0 + +mus_melancholy.ogg + URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/TO_CHILL_AND_STAY_AWAKE/Loyalty_Freak_Music_-_TO_CHILL_AND_STAY_AWAKE_-_06_People_are_spinning/ + author: Loyalty Freak Music + album: TO CHILL AND STAY AWAKE + songname: People are spinning + license: CC0 + +mus_melancholy_olivia.ogg + URL: https://freemusicarchive.org/music/Monplaisir/Pretty_and_Invisible/Monplaisir_-_Pretty_and_Invisible_-_02_Far_and_High/ + author: Monplaisir + album: Pretty and Invisible + songname: Far and High + license: CC0 + +mus_mia1.ogg + URL: https://freemusicarchive.org/music/holiznacc0/rock-montage/punk/ + author: HoliznaCC0 + album: Rock Montage + songname: Punk + license: CC0 + +mus_mia2.ogg + URL: https://freemusicarchive.org/music/holiznacc0/rock-montage/grunge/ + author: HoliznaCC0 + album: Rock Montage + songname: Grunge + license: CC0 +mus_misc.ogg + URL: https://freemusicarchive.org/music/holiznacc0/kick-it-laid-back-hiphop/unwind/ + author: HoliznaCC0 + album: Kick It (laid back HipHop) + songname: Unwind + license: CC0 + +mus_pissed.ogg + URL: https://freemusicarchive.org/music/Monplaisir/Pretty_and_Invisible/Monplaisir_-_Pretty_and_Invisible_-_06_Talentuous_and_Shy/ + author: Monplaisir + album: Pretty and Invisible + songname: Talentuous and Shy + license: CC0 + +mus_rock.ogg + URL: https://freesound.org/people/Jibey-/sounds/572947/ + author: Jibey- + songname: Rock music.wav + license: CC-BY + +mus_slow.ogg + URL: https://freemusicarchive.org/music/holiznacc0/lo-fi-and-chill/ramenmp3/ + author: Ramen.mp3 + album: Lo-fi And Chill + songname: ramenmp3 + license: CC0 + +mus_thoughts1.ogg + URL: https://freemusicarchive.org/music/Monplaisir/PIGEONS_ARE_THE_BEST/Monplaisir_-_PIGEONS_ARE_THE_BEST_-_01_Jump__Oh_Hi_Mark/ + author: Monplaisir + album: PIGEONS ARE THE BEST + songname: Jump ! Oh Hi Mark + license: CC0 + +mus_thoughts2 + URL: https://freemusicarchive.org/music/Monplaisir/Pretty_and_Invisible/Monplaisir_-_Pretty_and_Invisible_-_01_Loved_and_Respected/ + author: Monplaisir + album: Pretty and Invisible + songname: Loved and Respected + license: CC0 + +mus_titlescreen.ogg + URL: https://freemusicarchive.org/music/holiznacc0/be-happy-with-who-you-are/finding-yourself/ + author: HoliznaCC0 + album: Be Happy With Who You Are + songname: Finding Yourself + license: CC0 + +mus_trouble.ogg + URL: https://freemusicarchive.org/music/Loyalty_Freak_Music/TO_CHILL_AND_STAY_AWAKE/Loyalty_Freak_Music_-_TO_CHILL_AND_STAY_AWAKE_-_05_Traveling_in_your_mind/ + author: Loyalty Freak Music + album: TO CHILL AND STAY AWAKE + songname: Traveling in your mind + license: CC0 + +mus_upbeat.ogg + URL: https://freemusicarchive.org/music/holiznacc0/lo-fi-and-chill/busted-jazz + author: HoliznaCC0 + album: Lo-fi And Chill + songname: Busted Jazz + license: CC0 + +--- GRAPHICS --- + +"The Resort at Pelican Hill" by Ansel Adams +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +https://www.flickr.com/photos/prayitnophotography/8468342892/ + +"Inside 360 Restaurant" by Larry Koester +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +https://www.flickr.com/photos/larrywkoester/22976555815/ + +"Banquet set up - Novotel Century Hong Kong Hotel" by RichardToHKG +LICENSED UNDER ATTRIBUTION 3.0 UNPORTED (CC BY 3.0) +https://commons.wikimedia.org/wiki/File:Banquet_set_up_-_Novotel_Century_Hong_Kong_Hotel.jpg + +"Seattle from the Space Needle" by Peter Kaminski +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +https://www.flickr.com/photos/peterkaminski/5444827/ + +"Bar" by fr1da +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +https://www.flickr.com/photos/denero188_36624278/2090013005 + +"Pleading face" and "Crocodile emoji" by Twitter/X +Copyright 2020 Twitter, Inc and other contributors +Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ + +"Swing" by Julia Koefender +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +https://www.flickr.com/photos/juliakoefender/7128081493/ + +"Kitchen in our Hollywood Hills Hidden Gem apartment - Los Angeles, California, USA" by Glen Bowman +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) +https://www.flickr.com/photos/glenbowman/18180131123/ + +"Sunset with yachts near Phuket island, Thailand XOKA2050s" by Phuket@photographer.net +LICENSED UNDER ATTRIBUTION 2.0 (CC BY 2.0) https://www.flickr.com/photos/linvoyage/44910097244/ \ No newline at end of file diff --git a/README.md b/README.md index 2d229dc..cdcaa77 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,15 @@ Information regarding how to set up the full game as a Ren'Py project using the ## Building -There are two ways to launch this project/make distributions; Through the Ren'Py SDK or RenKit: +There are two options for building dependong on your preference: -- Ren'Py SDK - Good for computerlets/ease of use. Has debugging features and a bit more options. Requires a bit more setup for Android building. -- RenKit - Good for automation/terminal users. Dead simple setup, auto downloads all required files for building distributions, including the SDK and Android stuff. Allows CLI interfacing with Ren'Py to launch projects +- Ren'Py SDK - For being able to launch the project as a game and/or make personal distributions. Good for computerlets/ease of use. Has debugging features and a bit more options. Requires a bit more setup for Android building. Will not include workshop support by itself. + +- RenKit - For accurate compilation. Good for automation/terminal users. Dead simple setup, auto downloads almost all required files for building distributions, including the SDK and most Android stuff. Allows CLI interfacing with Ren'Py to launch projects. Includes workshop support via the patching functionality. ### BUILDING WITH THE SDK -1. [Download the Ren'Py 8.1.3 SDK depending on your system](https://www.renpy.org/release_list.html) +1. [Download the Ren'Py 8.2.1 SDK depending on your system](https://www.renpy.org/release_list.html) 2. Extract the .zip/.bz2/.dmg or get it through your package manager and run the SDK. 3. Clone this repo - Or if you don't use Git, click the '...' button near the topright of this page and click 'Download ZIP' - and place it within the Ren'Py SDK or to a directory of your choosing. 4. Launch the SDK and set the projects path to the folder containing your repo. Make sure the project is selected within the projects list. @@ -32,9 +33,10 @@ There are two ways to launch this project/make distributions; Through the Ren'Py 2. Extract the .zip/.xz or get it through your package manager. 3. Clone this repo - Or if you don't use Git, click the '...' button near the topright of this page and click 'Download ZIP' - and place it somewhere you can access. 4. Edit the `renconstruct.toml` file in the root of the project files to fit your needs like which distributions you want to build. +5. (OPTIONAL) If you want to include building for Android, you'll need to install JDK 21 as renconstruct can't do that automatically. [Read up the Ren'Py documentation for info on how](https://www.renpy.org/doc/html/android.html#step-1-installing-the-dependencies). 5. Run `renconstruct.exe` with the following command: -```bash -renconstruct build -c "/renconstruct.toml" "" "" +```bash renconstruct build "." dist/ +renconstruct build "" "" -c "/renconstruct.toml" ``` This will start the build process and outputs the game to whatever path you set your distributions to. @@ -43,11 +45,10 @@ This will start the build process and outputs the game to whatever path you set More information on the build process [can be found here](https://www.renpy.org/doc/html/build.html#building-distributions). -Information relating to building for Android [can be found here here as well](https://www.renpy.org/doc/html/android.html). Note that for Android, you must use Java SDK 8 until Wani updates to Ren'Py 8.2.0. - +Information relating to building for Android [can be found here here as well](https://www.renpy.org/doc/html/android.html). Information regarding RenKit and it's documentation [can be found on its github page](https://github.com/kobaltcore/renkit) -The 'woodpecker.yml' file included in the source is for reference in CI/CD and may be useful for some. +The 'woodpecker.yml' file included in the source is for reference in CI/CD and may be useful for some. With Woodpecker, versioning can be applied via git tags, and will replace the in-game version variable and the versioning for android with the name of the tag. It's configured for our servers so it's not expected to work right off the bat if you try it. ## Setting up the full game as a Ren'Py project diff --git a/build_patch/patch.diff b/build_patch/patch.diff new file mode 100644 index 0000000..c18a321 --- /dev/null +++ b/build_patch/patch.diff @@ -0,0 +1,29 @@ +233a234 +> import os # @UnresolvedImport +236a238,263 +> +> # MODS STUFF +> APP_ID = 1895350 +> +> if not renpy.android: +> try: # Android will try to run this for some reason and fail +> mod_paths = [ +> f"../../workshop/content/{APP_ID}/", +> f"/Users/{os.getlogin()}/Library/Application Support/Steam/steamapps/workshop/content/{APP_ID}/", +> os.path.expanduser(f"~/Library/Application Support/Steam/steamapps/workshop/content/{APP_ID}/") +> ] +> except: +> mod_paths = [] +> +> new_paths = [] +> for path in mod_paths: +> try: +> if os.path.exists(path): +> for name in os.listdir(path): +> full_path = os.path.join(path, name) +> if os.path.isdir(full_path): +> new_paths.append(full_path) +> except Exception as e: +> renpy.display.log.write(f"Error while adding mod search paths: {e}") +> +> searchpath += new_paths diff --git a/build_patch/patch.py b/build_patch/patch.py new file mode 100644 index 0000000..6015aa5 --- /dev/null +++ b/build_patch/patch.py @@ -0,0 +1,19 @@ +class PatchTask: + def __init__(self, config, input_dir, output_dir): + self.config = config + self.input_dir = input_dir + self.output_dir = output_dir + + def pre_build(self): + import subprocess + print("================Initiating patching==================") + + try: + subprocess.run(["patch", f"/tmp/cache/{self.config['ver']}/renpy.py", "./build_patch/patch.diff"]) + except Exception as e: + print(e) + raise e + print("================File Patched==================") + + def post_build(self): + pass diff --git a/game/00src/chapter_select.rpy b/game/00src/chapter_select.rpy index c579a2f..9898f72 100644 --- a/game/00src/chapter_select.rpy +++ b/game/00src/chapter_select.rpy @@ -9,13 +9,17 @@ label chapter_select: camera: resetcamera() yanchor 0.0 xanchor 0.0 rotate None zoom 1.0 + matrixcolor None - $ quick_menu = True - $ notagoodtime_ui = False - $ textbox_offset = 0 - $ namebox_offset = 0 - $ _window_alpha = 1.0 - $ broken_phone = False + python: + quick_menu = True + notagoodtime_ui = False + textbox_offset = 0 + namebox_offset = 0 + _window_alpha = 1.0 + broken_phone = False + renpy.hide_screen("_phone") + renpy.set_return_stack([]) # This prevents timeloop bugs call initstats from _call_initstats diff --git a/game/00src/credits.rpy b/game/00src/credits.rpy index cfbc39d..81a1a86 100644 --- a/game/00src/credits.rpy +++ b/game/00src/credits.rpy @@ -5,8 +5,6 @@ init 999 python: #hard code the header & footer #then iterate the list_credits - - list_credits_start = [ _("I Wani Hug That Gator"), _("By Cavemanon"), @@ -206,6 +204,35 @@ init 999 python: else: return list_credits[accessor][value] + SIZE_SNOT_GAMES = 55*3+10 + SIZE_TITLE = 32*3+10 + SIZE_ENTRY = 26*3+10 + SIZE_TL = 26*2+10 + SIZE_ENDER = 52*3+10 + +screen muh_credits(): + fixed: + xalign 0.5 + vbox: + xalign 0.5 + null width 1 height 16*25 + text list_credits_start[0] size SIZE_SNOT_GAMES xalign 0.5 yalign 0.5 text_align 0.5 outlines [ (absolute(5), "#000", absolute(0), absolute(0)) ] + null width 1 height 16*1 + text list_credits_start[1] size SIZE_TITLE xalign 0.5 yalign 0.5 text_align 0.5 outlines [ (absolute(5), "#000", absolute(0), absolute(0)) ] + null width 1 height 16*18 + text list_credits_start[2] size SIZE_ENTRY xalign 0.5 yalign 0.5 text_align 0.5 outlines [ (absolute(5), "#000", absolute(0), absolute(0)) ] + null width 1 height 16*12 + + for key, arr in list_credits.items(): + text key size SIZE_ENTRY xalign 0.5 yalign 0.5 text_align 0.5 outlines [ (absolute(5), "#000", absolute(0), absolute(0)) ] + null width 1 height 16*6 + vbox: + xalign 0.5 + spacing 17 + for item in arr: + text item size SIZE_ENTRY xalign 0.5 yalign 0.5 text_align 0.5 outlines [ (absolute(5), "#000", absolute(0), absolute(0)) ] + null width 1 height 16*3 + transform tf_credits_moveinright(x=0.0, y=0.0, duration=2.0, delay=0.0): subpixel True alpha 0.0 pause delay @@ -250,7 +277,14 @@ transform tf_credits_slightzoomout_offset(y=0.5, yanchor=0.5): subpixel True linear 7.5 zoom 0.95 +# This can take a string for the text size, intended for renpy's translation generation. The string has to follow this format: +# "(number)=(credits text)" +# So an example would be: +# "100=Directed By" screen endingtext(text="placeholder", text_transform=truecenter, text_align=0.0, text_size=100): + python: + if isinstance(text_size, str): + text_size = int(text_size[0:text_size.find("=")]) text text yanchor 0.0 xanchor text_align size text_size text_align text_align outlines [ (absolute(5), "#000", absolute(0), absolute(0)) ] at text_transform @@ -393,12 +427,13 @@ label credits: hide CAVEMANON # Display the whole credits list + show spr_credits: + crop None zoom 1.0 yalign 0.0 + if ending != 3: - show spr_credits: - crop (0.0, 0.0, 1.0, 17550) zoom 1.0 yalign 0.0 alpha 1.0 - else: - show spr_credits: - crop None zoom 1.0 yalign 0.0 + show black as censor: + ypos 16.3 + xsize 0.9 # We squeeze it so that it doesnt cover the (1) in ending 2 show spr_credits_theend: ypos 17.5 @@ -522,7 +557,7 @@ label e4credits_1: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[0], tf_credits_moveinright(0.15, 0.3, 7)) + show screen endingtext(list_credits_titles[0], tf_credits_moveinright(0.15, 0.3, 7), text_size=__("100=Directed by")) show screen endingtext(display_credits_names(0, 0), tf_credits_moveinleft(0.85, 0.5, 7), 1.0) as endingtext2 pause 7.5 @@ -552,7 +587,7 @@ label e4credits_2: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[1], tf_credits_moveinright(0.15, 0.1, 7)) + show screen endingtext(list_credits_titles[1], tf_credits_moveinright(0.15, 0.1, 7), text_size=__("100=Written by")) show screen endingtext(display_credits_names(1), tf_credits_moveinleft(0.85, 0.25, 7), 1.0) as endingtext2 pause 7.5 @@ -591,7 +626,7 @@ label e4credits_3: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[2], tf_credits_moveinright(0.15, 0.6, 7)) + show screen endingtext(list_credits_titles[2], tf_credits_moveinright(0.15, 0.6, 7), text_size=__("100=Story by")) show screen endingtext(display_credits_names(2), tf_credits_moveinleft(0.85, 0.45, 7), 1.0) as endingtext2 pause 7.5 @@ -616,7 +651,7 @@ label e4credits_4: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[3], tf_credits_moveinright(0.1, 0.6, 7), text_size=85) + show screen endingtext(list_credits_titles[3], tf_credits_moveinright(0.1, 0.6, 7), text_size=__("85=Production Coordinator")) show screen endingtext(display_credits_names(3), tf_credits_moveinleft(0.4, 0.75, 7), 1.0) as endingtext2 pause 7.5 @@ -660,7 +695,7 @@ label e4credits_5: camera: linear 15 ypos -1.5 - show screen endingtext(list_credits_titles[4], tf_credits_moveinright(0.15, 0.1, 14)) + show screen endingtext(list_credits_titles[4], tf_credits_moveinright(0.15, 0.1, 14), text_size=__("100=Artwork by")) show screen endingtext(display_credits_names(4, end_index=5), tf_credits_moveinleft(0.85, 0.2, 7), 1.0) as endingtext2 pause 7 @@ -737,7 +772,7 @@ label e4credits_6: alpha ALPHA_CONTROLLER shakeend(i=2.0) - show screen endingtext(list_credits_titles[5], tf_credits_moveinright(0.05, 0.1, 7)) + show screen endingtext(list_credits_titles[5], tf_credits_moveinright(0.05, 0.1, 7), text_size=__("100=Additional Artwork by")) show screen endingtext(display_credits_names(5), tf_credits_moveinleft(0.55, 0.3, 7), 1.0) as endingtext2 pause 7.5 @@ -783,7 +818,7 @@ label e4credits_7: xalign 0.5 yalign 0.5 zoom 1.1 linear 8 zoom 1.0 - show screen endingtext(list_credits_titles[6], tf_credits_moveinright(0.125, 0.45, 7)) + show screen endingtext(list_credits_titles[6], tf_credits_moveinright(0.125, 0.45, 7), text_size=__("100=Stock Photos by")) show screen endingtext(display_credits_names(6), tf_credits_moveinleft(0.85, 0.35, 7), 1.0) as endingtext2 pause 7.5 @@ -813,7 +848,7 @@ label e4credits_8: xpos 0.67 ypos 0.66 alpha 0.0 linear 0.5 alpha 1 - show screen endingtext(list_credits_titles[7], tf_credits_moveinright(0.1, 0.1, 14)) + show screen endingtext(list_credits_titles[7], tf_credits_moveinright(0.1, 0.1, 14), text_size=__("100=Animation by")) show screen endingtext(display_credits_names(7, end_index=3), tf_credits_moveinleft(0.9, 0.1, 7), 1.0) as endingtext2 pause 7 @@ -852,7 +887,7 @@ label e4credits_9: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[8], tf_credits_moveinright(0.35, 0.1, 14)) + show screen endingtext(list_credits_titles[8], tf_credits_moveinright(0.35, 0.1, 14), text_size=__("100=Programming by")) show screen endingtext(display_credits_names(8, end_index=3), tf_credits_moveinleft(0.85, 0.3, 7), 1.0) as endingtext2 pause 7 @@ -889,7 +924,7 @@ label e4credits_10: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[9], tf_credits_moveinright(0.1, 0.2, 7)) + show screen endingtext(list_credits_titles[9], tf_credits_moveinright(0.1, 0.2, 7), text_size=__("100=Music by")) show screen endingtext(display_credits_names(9), tf_credits_moveinleft(0.5, 0.4, 7), 1.0) as endingtext2 pause 7.5 @@ -914,7 +949,7 @@ label e4credits_11: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[10], tf_credits_moveinright(0.15, 0.15, 14)) + show screen endingtext(list_credits_titles[10], tf_credits_moveinright(0.15, 0.15, 14), text_size=__("100=Stock Music by")) show screen endingtext(display_credits_names(10, end_index=3), tf_credits_moveinleft(0.85, 0.35, 7), 1.0) as endingtext2 pause 7 @@ -956,7 +991,7 @@ label e4credits_12: ypos 0.25 xpos 0.01 linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[11], tf_credits_moveinright(0.3, 0.2, 7)) + show screen endingtext(list_credits_titles[11], tf_credits_moveinright(0.3, 0.2, 7), text_size=__("100=Stock Music acquired by")) show screen endingtext(display_credits_names(11), tf_credits_moveinleft(0.9, 0.4, 7), 1.0) as endingtext2 pause 7.5 @@ -988,7 +1023,7 @@ label e4credits_13: xpos 0.81 alpha 0.0 linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[12], tf_credits_moveinright(0.15, 0.2, 7)) + show screen endingtext(list_credits_titles[12], tf_credits_moveinright(0.15, 0.2, 7), text_size=__("100=Sound by")) show screen endingtext(display_credits_names(12), tf_credits_moveinleft(0.85, 0.4, 7), 1.0) as endingtext2 pause 7.5 @@ -1010,7 +1045,7 @@ label e4credits_14: parallel: linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[13], tf_credits_moveinright(0.15, 0.1, 14)) + show screen endingtext(list_credits_titles[13], tf_credits_moveinright(0.15, 0.1, 14), text_size=__("100=Stock Sounds by")) show screen endingtext(display_credits_names(13, end_index=4), tf_credits_moveinleft(0.85, 0.25, 7), 1.0) as endingtext2 pause 7 @@ -1039,7 +1074,7 @@ label e4credits_15: ypos 0.0 xpos 0.47 zoom 2.02 linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[13], tf_credits_moveinright(0.05, 0.1, 14)) + show screen endingtext(list_credits_titles[13], tf_credits_moveinright(0.05, 0.1, 14), text_size=__("100=Stock Sounds by")) show screen endingtext(display_credits_names(13, start_index=10, end_index=13), tf_credits_moveinleft(0.45, 0.3, 7), 1.0) as endingtext2 pause 7 @@ -1123,7 +1158,7 @@ label e4credits_16: linear 0.1 yoffset -1 repeat - show screen endingtext(list_credits_titles[14], tf_credits_moveinright(0.2, 0.1, 7)) + show screen endingtext(list_credits_titles[14], tf_credits_moveinright(0.2, 0.1, 7), text_size=__("100=Stock sounds acquired at")) show screen endingtext(display_credits_names(14), tf_credits_moveinleft(0.9, 0.3, 7), 1.0) as endingtext2 pause 7.5 @@ -1159,7 +1194,7 @@ label e4credits_final: xpos 0.12 ypos 0.24 alpha 0.0 linear 0.5 alpha ALPHA_CONTROLLER - show screen endingtext(list_credits_titles[15], tf_credits_moveinright(0.15, 0.15, 15.25)) + show screen endingtext(list_credits_titles[15], tf_credits_moveinright(0.15, 0.15, 15.25), text_size=__("100=Special Thanks")) show screen endingtext(display_credits_names(15, end_index=4), tf_credits_moveinleft(0.85, 0.25, 7), 1.0) as endingtext2 pause 7.5 diff --git a/game/00src/definitions/audio.rpy b/game/00src/definitions/audio.rpy index 09452e3..abc1404 100644 --- a/game/00src/definitions/audio.rpy +++ b/game/00src/definitions/audio.rpy @@ -31,9 +31,17 @@ define audio.mus_introspective_night = "" + DEFAULT_MUSIC_FILEPA define audio.mus_school_somber = "" + DEFAULT_MUSIC_FILEPATH + "mus_school_somber" + DEFAULT_MUSIC_EXTENSION define audio.mus_remorse = "" + DEFAULT_MUSIC_FILEPATH + "mus_remorse" + DEFAULT_MUSIC_EXTENSION define audio.mus_complete = "" + DEFAULT_MUSIC_FILEPATH + "mus_complete" + DEFAULT_MUSIC_EXTENSION +define audio.mus_boring = "" + DEFAULT_MUSIC_FILEPATH + "mus_boring" + DEFAULT_MUSIC_EXTENSION +define audio.mus_intense = "" + DEFAULT_MUSIC_FILEPATH + "mus_intense" + DEFAULT_MUSIC_EXTENSION +define audio.mus_romance3 = "" + DEFAULT_MUSIC_FILEPATH + "mus_romance3" + DEFAULT_MUSIC_EXTENSION +define audio.mus_introspective1 = "" + DEFAULT_MUSIC_FILEPATH + "mus_introspective1" + DEFAULT_MUSIC_EXTENSION +define audio.mus_dress = "" + DEFAULT_MUSIC_FILEPATH + "mus_dress" + DEFAULT_MUSIC_EXTENSION +define audio.mus_olivia = "" + DEFAULT_MUSIC_FILEPATH + "mus_olivia" + DEFAULT_MUSIC_EXTENSION +define audio.mus_happy1 = "" + DEFAULT_MUSIC_FILEPATH + "mus_happy1" + DEFAULT_MUSIC_EXTENSION +define audio.mus_credits_e4 = "" + DEFAULT_MUSIC_FILEPATH + "mus_credits_e4" + DEFAULT_MUSIC_EXTENSION define audio.mus_chill2 = "" + DEFAULT_MUSIC_FILEPATH + "mus_chill2" + DEFAULT_MUSIC_EXTENSION -define audio.mus_happy1 = "" + DEFAULT_MUSIC_FILEPATH + "mus_happy1" + DEFAULT_MUSIC_EXTENSION +define audio.mus_happy2 = "" + DEFAULT_MUSIC_FILEPATH + "mus_happy2" + DEFAULT_MUSIC_EXTENSION define audio.mus_damien = "" + DEFAULT_MUSIC_FILEPATH + "mus_damien" + DEFAULT_MUSIC_EXTENSION define audio.mus_upbeat = "" + DEFAULT_MUSIC_FILEPATH + "mus_upbeat" + DEFAULT_MUSIC_EXTENSION define audio.mus_chill_thinking = "" + DEFAULT_MUSIC_FILEPATH + "mus_chill_thinking" + DEFAULT_MUSIC_EXTENSION diff --git a/game/00src/definitions/characters.rpy b/game/00src/definitions/characters.rpy index ed0f0b0..7d1d7e4 100644 --- a/game/00src/definitions/characters.rpy +++ b/game/00src/definitions/characters.rpy @@ -1480,7 +1480,9 @@ layeredimage olivia hmph: xanchor 0.5 yoffset -150 mesh True gl_drawable_resolution False - always "spr_olivia_tail_normal_hmph" + group tails: + attribute tailsock "spr_olivia_tail_tailsock_neutral" + attribute notailsock default "spr_olivia_tail_normal_hmph" group wheelchair_back: attribute wheelchair default "spr_olivia_wheelchair_back" @@ -2445,15 +2447,15 @@ layeredimage vinny: attribute unswag "spr_null" group macaroni_left: #see images.rpy for definitions for the macaroni - attribute macaroni "macaroni_left" - attribute macaroni_up "macaroni_left_up" - attribute macaronibroken "macaroni_left_broken" + attribute macaroni "spr_macaroni_left" xoffset 250 yoffset 820 + attribute macaroni_up "spr_macaroni_left" xoffset 250 yoffset 760 + attribute macaronibroken "spr_macaroni_left" xoffset 220 yoffset 820 attribute nomacaroni "spr_null" group macaroni_right: - attribute macaroni "macaroni_right" - attribute macaroni_up "macaroni_right_up" - attribute macaronibroken "macaroni_right_broken" + attribute macaroni "spr_macaroni_right" xoffset 250 yoffset 820 + attribute macaroni_up "spr_macaroni_right" xoffset 250 yoffset 760 + attribute macaronibroken "spr_macaroni_right" xoffset 280 yoffset 820 attribute nomacaroni "spr_null" diff --git a/game/00src/definitions/images.rpy b/game/00src/definitions/images.rpy index 7655a2a..7c4c6d5 100644 --- a/game/00src/definitions/images.rpy +++ b/game/00src/definitions/images.rpy @@ -326,56 +326,19 @@ image full_box: contains: "bg_box_front" -# Vinny's Macaroni -image macaroni_right: - "spr_macaroni_right" - xoffset 250 yoffset 820 - -image macaroni_left: - contains: - "spr_macaroni_left_base" - xoffset 250 yoffset 820 - contains: - "spr_macaroni_left_text" - xoffset 250 yoffset 820 - -image macaroni_right_broken: - "spr_macaroni_right" - xoffset 280 yoffset 820 - -image macaroni_left_broken: - contains: - "spr_macaroni_left_base" - xoffset 220 yoffset 820 - contains: - "spr_macaroni_left_text" - xoffset 220 yoffset 820 - -image macaroni_right_up: - "spr_macaroni_right" - xoffset 250 yoffset 820 - "spr_macaroni_right" - yoffset 760 - #TODO: add transitions to this so it doesn't suck! - #easein_cubic yoffset 760 - -image macaroni_left_up: - contains: - "spr_macaroni_left_base" - xoffset 250 yoffset 820 - "spr_macaroni_left_base" - yoffset 760 - contains: - "spr_macaroni_left_text" - xoffset 250 yoffset 820 - "spr_macaroni_left_text" - yoffset 760 - # # DO NOT USE THE contains METHOD FOR PROPS ATTACHED TO CHARACTERS IN THE characters.rpy FILE. # For some reason, they bug out on positoning and alignment if you do, so use Composte() instead. # +# Vinny's Macaroni +image spr_macaroni_left = Composite( + (232, 147), + (0, 0), "spr_macaroni_left_base", + (0, 0), "spr_macaroni_left_text" +) + + # Inco's grocery bag image spr_mug_damien = Composite( (195, 206), diff --git a/game/00src/mod_menu.rpy b/game/00src/mod_menu.rpy index ef41202..1f4e7c5 100644 --- a/game/00src/mod_menu.rpy +++ b/game/00src/mod_menu.rpy @@ -1,36 +1,661 @@ +# This is one big clusterfuck so let's break it down: + +# All mods have a format that is strictly'ish followed - Metadata json files are a dict of values that represent a mod name, if they jump to a label, ID's, +# descriptions, etc. and are needed to load any mod scripts at all (And scripts are the only way mods can add anything into the game). Given Ren'Py's lack of support +# for namespaced variables, metadata files are designed as such that they don't have mod conflicts so all mods can have their metadata shown at once, even if +# their respective mod is disabled, so the metadata of alls mods gets loaded into mod_menu_metadata. + +# For this big ass init block, metadata loading has these phases: +# 1. Mod metadata collection (Searches through all files, checks for mod disabling files, and picks out metadata.json files) +# 2. Mod metadata sanitization/error handling (Searches through all metadata.json files, warning of errors and safeguards bad input) +# 3. Mod metadata image/script collection (Searches for images besides metadata.json, loading the thumbnail, icon, and screenshots if they're there, and language +# dependant metadata files such as jsons with translated strings or images) +# 4. Add to mod_menu_metadata (Metadata is added to the mod_menu_metadata list) +# 5. Mod order organizing (Metadata is initially loaded in file alphabetical order, so it rearranges mod_menu_metadata according to load order +# and applies whether a mod is supposed to be disabled or not) +# 6. Mod loading (Loads mod scripts according to mod_menu_metadata) + +# Entries that don't exist in mod_menu_metadata or end up as 'None' by the end of the mod loading should use the .get method for accessing values. This way modders +# don't need to implement every single entry into metadata.json (But makes the code here messier) + +# When an index in mod_menu_metadata is filled out, each mod should look a little something like this (but more sporadically organized), depending on what's +# actually filled out in the metadata files: +# { +# "ID": "my_mod_id", +# "Enabled: True +# "Scripts": [ "mods/My Mod/my_mod_script1", "mods/My Mod/my_mod_script2", "mods/My Mod/my_mod_script3" ] +# "Label": "my_mod_label", +# "None": { +# "Name": "My Mod Name" +# "Version": "1.0", +# "Authors": [ "Author1", "Author2", "Author3" ], +# "Links": "Link1", +# "Description": "My Mod Description", +# "Mobile Description": "My Mod Description", +# "Display": "icon", +# "Thumbnail Displayable": "my_thumbnail_displayable" +# "Icon Displayable": "my_icon_displayable" +# "Screenshot Displayables": [ "my_screenshot_displayable1", "my_screenshot_displayable2", "mods/My Mod/screenshot3.png", "my_screenshot_displayable4" ] +# "Thumbnail": "mods/My Mod/thumbnail.png" +# "Icon": "mods/My Mod/icon.png" +# "Screenshots": [ "mods/My Mod/screenshot1.png", "mods/My Mod/screenshot2.png", "mods/My Mod/screenshot3.png" ] +# }, +# "es": +# "Name": "My Mod Name but in spanish" +# "Version": "1.0", +# "Authors": [ "Author1", "Author2", "Author3" ], +# "Links": "Link1", +# "Description": "My Mod Description but in spanish", +# "Mobile Description": "My Mod Description but in spanish", +# "Display": "icon", +# "Thumbnail Displayable": "my_thumbnail_displayable_es" +# "Icon Displayable": "my_icon_displayable_es" +# "Screenshot Displayables": [ "my_screenshot_displayable1_es", "my_screenshot_displayable2_es", "mods/My Mod/screenshot3.png", "my_screenshot_displayable4_es" ] +# "Thumbnail": "mods/My Mod/thumbnail_es.png" +# "Icon": "mods/My Mod/icon_es.png" +# "Screenshots": [ "mods/My Mod/screenshot1_es.png", "mods/My Mod/screenshot2.png", "mods/My Mod/screenshot3_es.png" ] +# }, +# "ru": { +# "Name": "My Mod Name but in russian" +# etc... +# }, +# etc... +# } +# +# Note that some keys may exist, but simple be 'None', likely as a result of improperly filled out metadata files. + + +init -999 python: + import json + from enum import Enum + + # + # Modding system setup + # + + class ModError(Enum): + Metadata_Fail = 0 + Name_Not_String = 1 + Label_Not_String = 2 + Display_Not_String = 3 + Display_Invalid_Mode = 4 + Version_Not_String = 5 + Authors_Not_String_Or_List = 6 + Authors_Contents_Not_String = 7 + Links_Not_String_Or_List = 8 + Links_Contents_Not_String = 9 + Description_Not_String = 10 + Mobile_Description_Not_String = 11 + Screenshot_Displayables_Not_List = 12 + Screenshot_Displayables_Contents_Not_String = 13 + Icon_Displayable_Not_String = 14 + Thumbnail_Displayable_Not_String = 15 + No_ID = 16 + ID_Not_String = 17 + Similar_IDs_Found = 18 + Installed_Incorrectly = 19 + Invalid_Image_Extension = 20 + Invalid_Language_Code = 21 + + + mods_dir = "mods/" # The root mod folder. Important that you keep the slash at the end. + mod_menu_moddir = ".../game/" + mods_dir # The visual mod dir name + mod_menu_access = [] # This variable exists for legacy mods to be usable. + + # A list containing tuples that contain a mod's ID and if the mod is enabled. The game references this list to activate mods and order them from + # first to last index + if persistent.enabled_mods == None: + persistent.enabled_mods = [] + + valid_image_filetypes = [ ".png", ".jpg", ".webp", ".avif", ".svg", ".gif", ".bmp" ] + + # Makes loading mods on android possible. It creates folders for android mods, changing mod_menu_moddir as necessary if the user is playing on Android. + if renpy.android and not config.developer: + android_mods_path = os.path.join(os.environ["ANDROID_PUBLIC"], "game", mods_dir) + try: + # We have to create both the 'game' and 'mods' folder for android. + os.mkdir(mods_dir) + os.mkdir(android_mods_path) + except: + pass + + mod_menu_moddir = android_mods_path + + # Determines if a filename with it's extension is valid for renpy's image displaying and for our specified mod metadata. + def is_valid_metadata_image(filename, name_of_mod): + error_trigger = True + + for ext in valid_image_filetypes: + if this_file.endswith(ext): + error_trigger = False + if error_trigger: + mod_menu_errorcodes.append([ ModError.Invalid_Image_Extension, { "mod_name": mod_name, "name_of_file": filename }]) + return False + + return True + + + # Start finding mods. Find json files within each folder and mod disablers in the mods directory if there's any. + # NOMODS - Disables mod loading entirely + # NOLOADORDER - Erases load order/mod states. + # DISABLEALLMODS - Temporarily disables all mods, but still loading their metadata, and returns them back to their original state when this is removed. + all_mod_files = [ i for i in renpy.list_files() if i.startswith(mods_dir) ] + load_metadata = True + load_mods = True + loadable_mod_metadata = [] + for i in all_mod_files: + if i.startswith(mods_dir + "NOMODS"): + load_metadata = False + loadable_mod_metadata = [] + if i.startswith(mods_dir + "NOLOADORDER"): + persistent.enabled_mods.clear() + if i.startswith(mods_dir + "DISABLEALLMODS"): + load_mods = False + + if load_metadata and i.endswith("/metadata.json"): + loadable_mod_metadata.append(i) + + # + # Get and store metadata + # + + # Where all mod info will be stored + mod_menu_metadata = [] + # A list that contains tuples that contain an error code and a dictionary containing metadata regarding the error such as + # which mod it's referring to. Used to construct error strings outside of this python block, to workaround Ren'Py's + # translation system not working this early. + mod_menu_errorcodes = [] + # Contains the mod_name's of previous mods that have successfully loaded, mirroring mod_menu_metadata while in this loop. Only used for ID checking. + mod_name_list = [] + + for file in loadable_mod_metadata: + mod_data_final = {} + mod_jsonfail_list = [] # List of langauges that has an associated metadata that failed to load. + mod_preferred_modname = [] + mod_exception = False + mod_in_root_folder = file.count("/", len(mods_dir)) is 0 + mod_folder_name = file.split("/")[-2] + # mod_name is used only to display debugging information via mod_menu_errorcodes. Contains the mod folder name and whatever translations of + # the mod's name that exist. Kind of a cursed implemnetation but with how early error reporting this is before solidifying the mod name + # this is just what I came up with. + # Other than what's directly defined here, it will contain mod names by their language code if they exist. English will go by the "None" key. + mod_name = {} + if mod_in_root_folder: + mod_name["Folder"] = None # 'None' will make it default to 'in root of mods folder' when used in the errorcode conversion. + else: + mod_name["Folder"] = mod_folder_name + + + # Quickly get the names of mods for debugging information, and in the process get raw values from each metadata file that exists. + + # Make the base metadata file (english) organized like a lang_data object, moving the ID to the mod_data_final object. + try: + mod_data = json.load(renpy.open_file(file)) + except Exception as e: + if mod_in_root_folder: + print("//////////// ERROR IN ROOT FOLDER MOD:") + else: + print(f"//////////// ERROR IN MOD '{mod_folder_name}':") + print(" "+str(e)) + print("//////////// END OF ERROR") + mod_exception = True + mod_jsonfail_list.append("None") + + if not mod_jsonfail_list: + if _preferences.language == None and isinstance(mod_data.get("Name"), str): + mod_name["None"] = mod_data["Name"] + + # Move these non-language specific pairs out of the way, into the base of the final mod dict. + if "ID" in mod_data.keys(): + mod_data_final["ID"] = mod_data["ID"] + del mod_data["ID"] + if "Label" in mod_data.keys(): + mod_data_final["Label"] = mod_data["Label"] + del mod_data["Label"] + # Then store the rest like any other language, just our default one. + mod_data_final['None'] = mod_data + + # Find language metadata files in the same place as our original metadata file, and then get values from it. + for lang in renpy.known_languages(): + lang_file = file[:-5] + "_" + lang + ".json" + if renpy.loadable(lang_file): + try: + lang_data = (json.load(renpy.open_file(lang_file))) + except Exception as e: + if mod_in_root_folder: + print(f"//////////// ERROR FOR {lang} METADATA IN ROOT FOLDER MOD:") + else: + print(f"//////////// ERROR FOR {lang} METADATA IN MOD '{mod_folder_name}':") + print(" "+str(e)) + print("//////////// END OF ERROR") + mod_jsonfail_list.append(lang) + + # Attempt to use this mod's translation of it's name if it matches the user's language preference. + if not lang in mod_jsonfail_list: + if _preferences.language == lang and isinstance(lang_data.get("Name"), str): + mod_name[lang] = lang_data["Name"] + mod_data_final[lang] = lang_data + + # Finally report if any of the jsons failed to load, now that we have the definitive list of mod names we could display. + for lang_code in mod_jsonfail_list: + mod_menu_errorcodes.append([ ModError.Metadata_Fail, { "mod_name": mod_name, "lang_code": lang_code }]) + + # + # Sanitize/Clean metadata values + # + + # Make sure our main metadata loaded + if not "None" in mod_jsonfail_list: + if mod_data_final.get("Label") != None and not isinstance(mod_data_final.get("Label"), str): + mod_menu_errorcodes.append([ ModError.Label_Not_String, { "mod_name": mod_name }]) + mod_data_final["Label"] = None + + # If we don't have an ID, don't put it in the mod loader + if mod_data_final.get("ID") == None: + mod_menu_errorcodes.append([ ModError.No_ID, { "mod_name": mod_name }]) + mod_exception = True + elif not isinstance(mod_data_final["ID"], str): + mod_menu_errorcodes.append([ ModError.ID_Not_String, { "mod_name": mod_name }]) + mod_exception = True + else: + # Detect already loaded metadata that has the same ID as our current one, and if so, don't load them + # We'll never get a match for the first mod loaded, so this is fine. + for i, x in enumerate(mod_menu_metadata): + if x["ID"] == mod_data_final["ID"]: + mod_menu_errorcodes.append([ ModError.Similar_IDs_Found, { "mod_name": mod_name, "mod_name2": mod_name_list[i] }]) + mod_exception = True + break + + # Since lang keys will only be added to the mod data dict if their respective metadata successfully loaded, no need to check. + for lang_key in mod_data_final.keys(): + if lang_key is "None" or lang_key in renpy.known_languages(): + lang_data = mod_data_final[lang_key] + + # The JSON object returns an actual python list, but renpy only works with it's own list object and the automation for this fails with JSON. + for x in lang_data.keys(): + if type(lang_data[x]) == python_list: + lang_data[x] = renpy.revertable.RevertableList(lang_data[x]) + + # Automatically give the name of the mod from the folder it's using if there's no defined name, but report an error if one is defined but not a string + if lang_data.get("Name") != None and not isinstance(lang_data.get("Name"), str): + if lang_data.get("Name") != None: + mod_menu_errorcodes.append([ ModError.Name_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + + lang_data["Name"] = mod_folder_name + + # Default "Display" to 'both' mode + if lang_data.get("Display") != None: + if not isinstance(lang_data.get("Display"), str): + if lang_data.get("Display") != None: + mod_menu_errorcodes.append([ ModError.Display_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Display"] = "both" + elif lang_data["Display"] not in ["both","icon","name"]: + mod_menu_errorcodes.append([ ModError.Display_Invalid_Mode, { "mod_name": mod_name, "display_mode": lang_data["Display"], "lang_code": lang_key }]) + lang_data["Display"] = "both" + + if lang_data.get("Version") != None and not isinstance(lang_data.get("Version"), str): + mod_menu_errorcodes.append([ ModError.Version_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Version"] = None + + # See if "Authors" is a list or string, and if it's a list search through the contents of the list to check if they're valid strings + if lang_data.get("Authors") != None and (not isinstance(lang_data.get("Authors"), str) and not isinstance(lang_data.get("Authors"), list)): + mod_menu_errorcodes.append([ ModError.Authors_Not_String_Or_List, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Authors"] = None + elif isinstance(lang_data.get("Authors"), list): + # Search through and call out entries that aren't strings + for i, s in enumerate(lang_data["Authors"]): + if not isinstance(s, str): + mod_menu_errorcodes.append([ ModError.Authors_Contents_Not_String, { "mod_name": mod_name, "author_number": i + 1, "lang_code": lang_key }]) + # And then mutate the list to only include strings + lang_data["Authors"][:] = [x for x in lang_data["Authors"] if isinstance(x, str)] + + if lang_data["Authors"] == []: + lang_data["Authors"] = None + + # Do the same as 'Authors' to 'Links' + if lang_data.get("Links") != None and (not isinstance(lang_data.get("Links"), str) and not isinstance(lang_data.get("Links"), list)): + mod_menu_errorcodes.append([ ModError.Links_Not_String_Or_List, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Links"] = None + elif isinstance(lang_data.get("Links"), list): + for i, s in enumerate(lang_data["Links"]): + if not isinstance(s, str): + mod_menu_errorcodes.append([ ModError.Links_Contents_Not_String, { "mod_name": mod_name, "link_number": i + 1, "lang_code": lang_key }]) + lang_data["Links"][:] = [x for x in lang_data["Links"] if isinstance(x, str)] + + if lang_data["Links"] == []: + lang_data["Links"] = None + + if lang_data.get("Description") != None and not isinstance(lang_data.get("Description"), str): + mod_menu_errorcodes.append([ ModError.Description_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Description"] = None + + if lang_data.get("Mobile Description") != None and not isinstance(lang_data.get("Mobile Description"), str): + mod_menu_errorcodes.append([ ModError.Mobile_Description_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Mobile Description"] = None + + if lang_data.get("Screenshot Displayables") != None: + if not isinstance(lang_data.get("Screenshot Displayables"), list): + mod_menu_errorcodes.append([ ModError.Screenshot_Displayables_Not_List, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Screenshot Displayables"] = None + else: + # Instead of remaking the list to only include strings, replace the non-strings with empty strings ("") so subsequent strings will still be in the right + # place when eventually showing displayable screenshots over non-displayable ones + for i, s in enumerate(lang_data["Screenshot Displayables"]): + if not isinstance(s, str): + mod_menu_errorcodes.append([ ModError.Screenshot_Displayables_Contents_Not_String, { "mod_name": mod_name, "screenshot_number": i + 1, "lang_code": lang_key }]) + s = "" + + if lang_data["Screenshot Displayables"] == []: + lang_data["Screenshot Displayables"] = None + + if lang_data.get("Icon Displayable") != None and not isinstance(lang_data.get("Icon Displayable"), str): + mod_menu_errorcodes.append([ ModError.Icon_Displayable_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Icon Displayable"] = None + + if lang_data.get("Thumbnail Displayable") != None and not isinstance(lang_data.get("Thumbnail Displayable"), str): + mod_menu_errorcodes.append([ ModError.Thumbnail_Displayable_Not_String, { "mod_name": mod_name, "lang_code": lang_key }]) + lang_data["Thumbnail Displayable"] = None + + # If our mod does not follow the structure of 'mods/Mod Name/metadata.json', don't load them + this_mod_dir = file[len(mods_dir):-len("metadata.json")] # If the metadata file is in another place, this will connect the filepath between it and the mods folder. + if file.count("/", len(mods_dir)) > 1: + good_folder = "'{color=#ffbdbd}" + mods_dir + mod_folder_name + "/{/color}'" + curr_folder = "'{color=#ffbdbd}" + mods_dir + this_mod_dir + "{/color}'" + mod_menu_errorcodes.append([ ModError.Installed_Incorrectly, { "mod_name": mod_name, "good_dir": good_folder, "curr_dir": curr_folder }]) + mod_exception = True + elif mod_in_root_folder: + mod_menu_errorcodes.append([ ModError.Installed_Incorrectly, { "mod_name": mod_name, "good_dir": mods_dir + "My Mod/", "curr_dir": mods_dir }]) + mod_exception = True + + # + # Collect mod scripts and metadata images + # + + mod_scripts = [] + mod_screenshots = {} + this_mod_dir_length = len(mods_dir + this_mod_dir) + for i in all_mod_files: + if i.startswith(mods_dir + this_mod_dir): + # Collect mod scripts + if not mod_exception and i.endswith(".rpym"): + mod_scripts.append(i[:-5]) + continue + + # This will only allow files that are at the root of the mod folder and have one period. + elif i.count("/", this_mod_dir_length) == 0 and i.count(".", this_mod_dir_length) == 1: + this_file = i[this_mod_dir_length:] + + if this_file.startswith("thumbnail."): + if is_valid_metadata_image(this_file, mod_name): + mod_data_final["None"]["Thumbnail"] = i + elif this_file.startswith("thumbnail_"): + trimmed_string = this_file[len("thumbnail_"):this_file.find(".")] + for lang in renpy.known_languages(): + if lang == trimmed_string: + if is_valid_metadata_image(this_file, mod_name): + mod_data_final[lang]["Thumbnail"] = i + + + elif this_file.startswith("icon."): + if is_valid_metadata_image(this_file, mod_name): + if mod_data_final.get("None") == None: + mod_data_final["None"] = {} + mod_data_final["None"]["Icon"] = i + elif this_file.startswith("icon_"): + trimmed_string = this_file[len("icon_"):this_file.find(".")] + for lang in renpy.known_languages(): + if lang == trimmed_string: + if is_valid_metadata_image(this_file, mod_name): + if mod_data_final.get(lang) == None: + mod_data_final["None"] = {} + mod_data_final[lang]["Icon"] = i + + + elif this_file.startswith("screenshot"): + # Disect the string after "screenshot" for the number and possible lang code + trimmed_string = this_file[len("screenshot"):this_file.find(".")] + number = "" + lang_code = "" + seperator = trimmed_string.find("_") + if seperator != -1: + number = trimmed_string[:seperator] + lang_code = trimmed_string[seperator + 1:] + else: + number = trimmed_string + + # See if we can extract the number + try: + converted_number = int(number) + except: + continue + + if not is_valid_metadata_image(this_file, mod_name): + continue + + if seperator == -1: + if mod_screenshots.get("None") == None: + mod_screenshots["None"] = [] + mod_screenshots["None"].append((i, converted_number)) + elif lang_code in renpy.known_languages(): + if mod_screenshots.get(lang_code) == None: + mod_screenshots[lang_code] = [] + mod_screenshots[lang_code].append((i, converted_number)) + + # Refine collected screenshots so that translated screenshots use the english screenshots (the ones without a lang code) + # as a base, and then either replacing or adding the translated screenshots according to their number. + + for lang_key in mod_screenshots.keys(): + if lang_key != "None": + if mod_screenshots.get("None") == None: + temp_list_with_indexes = mod_screenshots[lang_key] + else: + temp_list_with_indexes = mod_screenshots["None"] + for i, lang_images in enumerate(mod_screenshots[lang_key]): + u = 0 + while u < len(temp_list_with_indexes): + if lang_images[1] > temp_list_with_indexes[u][1] and u == len(temp_list_with_indexes) - 1: + temp_list_with_indexes.append(lang_images) + break + elif lang_images[1] == temp_list_with_indexes[u][1]: + temp_list_with_indexes[u] = lang_images + break + elif lang_images[1] < temp_list_with_indexes[u][1]: + temp_list_with_indexes.insert(u, lang_images) + break + + u += 1 + else: + temp_list_with_indexes = mod_screenshots["None"] + + # Get rid of the tuples and just leave the screenshot files + mod_data_final[lang_key]["Screenshots"] = [] + for i in temp_list_with_indexes: + mod_data_final[lang_key]["Screenshots"].append(i[0]) + + # Make a copy of the current screenshots list, then put displayable screenshots wherever the values of "Screenshot Displayables" correspond in this list + # mod_screenshots will return an empty list if there were no screenshot files to begin with, so this works fine. + for lang_key in mod_data_final.keys(): + if lang_key is "None" or lang_key in renpy.known_languages(): + if mod_data_final[lang_key].get("Screenshots") == None: + mod_screenshots = [] + else: + mod_screenshots = mod_data_final[lang_key]["Screenshots"] + + if mod_data_final[lang_key].get("Screenshot Displayables") != None: + mod_displayable_list = mod_screenshots.copy() + + for i, x in enumerate(mod_data_final[lang_key]["Screenshot Displayables"]): + if i < len(mod_screenshots): + if x != "": + mod_displayable_list[i] = x + else: + mod_displayable_list.append(x) + + if mod_displayable_list != []: + mod_data_final[lang_key]["Screenshot Displayables"] = mod_displayable_list + else: + mod_data_final[lang_key]["Screenshot Displayables"] = None + + + # Don't load the mod if there's mod breaking errors + if mod_exception: + continue + + # Store the collected scripts and screenshots + mod_data_final["Scripts"] = mod_scripts + + # Make our mod loadable + mod_menu_metadata.append(mod_data_final) + mod_name_list.append(mod_name) # This will mirror mod_menu_metadata + + + # Sort mod metadata list according to enabled_mods, while dropping mods from enabled_mods if they aren't installed + # This will also apply the state of a mod if it's supposed to be enabled/disabled + # The effect will be that mod_menu_metadata is sorted from first to last to be loaded + temp_list = [] + for saved_mod_id, saved_mod_state in persistent.enabled_mods: + for mod in mod_menu_metadata: + if mod["ID"] == saved_mod_id: + mod["Enabled"] = saved_mod_state + temp_list.append(mod) + break + + # Now inverse search to find new mods and append them to metadata list. New mods are by default enabled, and are the last to be loaded + for mod in mod_menu_metadata: + mod_not_found = True + for saved_mod_id in persistent.enabled_mods: + if mod["ID"] == saved_mod_id[0]: + mod_not_found = False + if mod_not_found: + mod["Enabled"] = persistent.newmods_default_state + temp_list.append(mod) + + mod_menu_metadata = temp_list + + # Rewrite enabled_mods to reflect the new mod order, and load all the mods + persistent.enabled_mods.clear() + for mod in mod_menu_metadata: + persistent.enabled_mods.append( [ mod["ID"], mod["Enabled"] ] ) + + # Making the load_mods check here makes it so the NOLOAD flag doesn't overwrite the previously saved load order + if load_mods and mod["Enabled"]: + for script in mod["Scripts"]: + renpy.include_module(script) + else: + mod["Enabled"] = False + +# Now convert our errorcodes to errorstrings +init python: + def return_translated_mod_name(mod_dict): + if _preferences.language == None and "None" in mod_dict.keys(): + return "'{color=#ffbdbd}" + mod_dict["None"] + "{/color}'" + elif _preferences.language in mod_dict.keys(): + return "'{color=#ffbdbd}" + mod_dict[_preferences.language] + "{/color}'" + else: + if mod_dict["Folder"] == None: + return __("the root of the mods folder") + else: + return "'{color=#ffbdbd}" + mod_dict["Folder"] + "{/color}'" + def convert_errorcode_to_errorstring(error_code, var_dict): + if var_dict.get("mod_name") != None: + mod_name = return_translated_mod_name(var_dict["mod_name"]) + if var_dict.get("mod_name2") != None: + mod_name2 = return_translated_mod_name(var_dict["mod_name2"]) + + lang_code = var_dict.get("lang_code") + if lang_code == "None" or lang_code == None: + lang_code = "" + else: + lang_code = __(" for '{color=#ffbdbd}") + lang_code + __("{/color}' language") + + name_of_file = var_dict.get('name_of_file') + if name_of_file != None: + name_of_file = "'{color=#ffbdbd}" + name_of_file + "{/color}'" + + if error_code == ModError.Metadata_Fail: + if lang_code == "": + return __("{color=#ff1e1e}Mod in ") + mod_name + __(" failed to load: Metadata is formatted incorrectly. Check log.txt or console for more info.{/color}") + else: + return __("{color=#ff8b1f}Metadata in ") + mod_name + lang_code + _(" is formatted incorrectly. Check log.txt or console for more info.{/color}") + elif error_code == ModError.Name_Not_String: + return __("{color=#ff8b1f}Mod's name in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Label_Not_String: + return __("{color=#ff8b1f}Mod's label in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Display_Not_String: + return __("{color=#ff8b1f}Display mode in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Display_Invalid_Mode: + return __("{color=#ff8b1f}Display mode in ") + mod_name + lang_code + _(" is not valid. Valid options are 'both', 'icon' and 'name', not ") + var_dict['display_mode'] + __(".{/color}") + elif error_code == ModError.Version_Not_String: + return __("{color=#ff8b1f}Mod's version in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Authors_Not_String_Or_List: + return __("{color=#ff8b1f}Mod's authors in ") + mod_name + lang_code + _(" is not a string or list.{/color}") + elif error_code == ModError.Authors_Contents_Not_String: + return __("{color=#ff8b1f}Author ") + var_dict['author_number'] + __(" in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Links_Not_String_Or_List: + return __("{color=#ff8b1f}Mod's links in ") + mod_name + lang_code + __(" is not a string or list.{/color}") + elif error_code == ModError.Links_Contents_Not_String: + return __("{color=#ff8b1f}Link ") + var_dict['link_number'] + __(" in ") + mod_name + lang_code + __(" is not a string.{/color}") + elif error_code == ModError.Description_Not_String: + return __("{color=#ff8b1f}Mod's description in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Mobile_Description_Not_String: + return __("{color=#ff8b1f}Mod's mobile description in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Screenshot_Displayables_Not_List: + return __("{color=#ff8b1f}Mod's screenshot displayables in ") + mod_name + lang_code + _(" is not a list.{/color}") + elif error_code == ModError.Screenshot_Displayables_Contents_Not_String: + return __("{color=#ff8b1f}Screenshot Displayable ") + var_dict['screenshot_number'] + __(" in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Icon_Displayable_Not_String: + return __("{color=#ff8b1f}Mod's icon displayable in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.Thumbnail_Displayable_Not_String: + return __("{color=#ff8b1f}Mod's thumbnail displayable in ") + mod_name + lang_code + _(" is not a string.{/color}") + elif error_code == ModError.No_ID: + return __("{color=#ff1e1e}Mod in ") + mod_name + __(" failed to load: Does not have a mod ID.{/color}") + elif error_code == ModError.ID_Not_String: + return __("{color=#ff1e1e}Mod in ") + mod_name + __(" failed to load: ID is not a string.{/color}") + elif error_code == ModError.Similar_IDs_Found: + return __("{color=#ff1e1e}Mod in ") + mod_name + __(" failed to load: Another mod ") + mod_name2 + __(" has the same ID.{/color}") + elif error_code == ModError.Installed_Incorrectly: + return __("{color=#ff1e1e}Mod in ") + mod_name + __(" is not installed correctly.\nMake sure it's structure is ") + var_dict['good_dir'] + __(" instead of ") + var_dict['curr_dir'] + __(".{/color}") + elif error_code == ModError.Invalid_Image_Extension: + return __("{color=#ff8b1f}") + name_of_file + __(" image for mod in ") + mod_name + lang_code + _(" has an incompatible file extension. {a=https://www.renpy.org/doc/html/displayables.html#images}Only use images that Ren'Py supports!{/a}{/color}") + # Mod Menu screen ############################################################ ## ## Handles jumping to the mods scripts ## Could be more lean but if this is going to one of last time I touch the UI, ## then fine by me ## -#similar to quick_button funcs -screen mod_menu_button(filename, label, function): - button: - xmaximum 600 - ymaximum 129 - action function - if 'Back' in label or 'Return' in label or 'Quit' in label or 'Main Menu' in label: - activate_sound "audio/ui/snd_ui_back.wav" - else: - activate_sound "audio/ui/snd_ui_click.wav" - fixed: - add filename xalign 0.5 yalign 0.5 yzoom 1.2 xzoom 1.4 - text label xalign 0.5 yalign 0.5 size 34 - -# arr is [{ -# 'Name': string (name that appears on the button) -# 'Label': string (jump label) -# }, { .. } ] -# Reuse the same image string and keep things 'neat'. -screen mod_menu_buttons(filename, arr): - for x in arr: - use mod_menu_button(filename, x['Name'], Start(x['Label']) ) transform tf_modmenu_slide: xoffset 600 linear 0.25 xoffset 0 +# Some gay python workarounds for screen jank +init python: + def toggle_persistent_mods(index): + if persistent.enabled_mods[index][1] == True: + persistent.enabled_mods[index][1] = False + elif persistent.enabled_mods[index][1] == False: + persistent.enabled_mods[index][1] = True + def swapList(sl,pos1,pos2): + temp = sl[pos1] + sl[pos1] = sl[pos2] + sl[pos2] = temp + def swapMods(idx1,idx2): + swapList(mod_menu_metadata,idx1,idx2) + swapList(persistent.enabled_mods,idx1,idx2) + + # All operations that use this function need to be able to parse "None" as a safeguard. + def return_translated_metadata(mod_metadata, key): + if _preferences.language != None and _preferences.language in mod_metadata.keys() and mod_metadata[_preferences.language].get(key) != None: + return mod_metadata[_preferences.language][key] + elif "None" in mod_metadata.keys(): + return mod_metadata["None"].get(key) + else: + return None + + +default persistent.seenModWarning = False + screen mod_menu(): key "game_menu" action ShowMenu("extras") tag menu @@ -41,71 +666,342 @@ screen mod_menu(): add "gui/title_overlay.png" add "gui/overlay/sidemenu.png" at tf_modmenu_slide -#side_yfill True - #vbox: - # xpos 1940 - # yalign 0.03 - # if persistent.splashtype == 1: - # add "gui/sneedgame.png" - # else: - # add "gui/snootgame.png" + default mod_metadata = {} + default reload_game = False + default mod_button_enabled = True + default mod_button_alpha = 1.0 + default mod_screenshot_list = None + default mod_icon = None + default mod_thumbnail = None + + button at tf_modmenu_slide: + xpos 1455 + ypos 150 + xmaximum 300 + ymaximum 129 + # For some reason, Function() will instantly reload the game upon entering the mod menu, and put it in an infinite loop, so it's using a workaround + # with this variable. + action SetScreenVariable("reload_game", True) + activate_sound "audio/ui/snd_ui_click.wav" + + add "gui/button/menubuttons/menu_button.png" xalign 0.5 yalign 0.5 + text _("Reload Mods") xalign 0.5 yalign 0.5 size 34 + + if reload_game: + python: + reload_game = False + renpy.reload_script() + viewport at tf_modmenu_slide: - yinitial 0 - xpos 1885-540 - ypos 200 + xpos 1338 + ypos 279 xmaximum 540 - ymaximum 1080 - 200 + ymaximum 790 - if len(mod_menu_access) > 5: # Hides the scrollbar when not needed. Ideally nobody would install more than one mod at the same time, but oh well - scrollbars "vertical" + scrollbars "vertical" + vscrollbar_unscrollable "hide" mousewheel True draggable True pagekeys True - if len(mod_menu_access) != 0: + if len(mod_menu_metadata) != 0 or len(mod_menu_access) != 0: vbox: - #use mod_menu_button("gui/button/menubuttons/menu_button.png", _("Return"), ShowMenu("main_menu")) - #spacing 18 - use mod_menu_buttons("gui/button/menubuttons/menu_button.png", mod_menu_access ) + at truecenter + for i, x in enumerate(mod_menu_metadata): + hbox: + xsize 129 + ysize 129 + + vbox: + at truecenter + style_prefix None + spacing 5 + ysize 200 + # Move mod up button + if i!=0: + button: + at truecenter + style_prefix "main_menu" + add Null(30,30) + + activate_sound "audio/ui/snd_ui_click.wav" + + idle_foreground Transform("gui/button/menubuttons/up.png",xalign=0.5,yalign=0.5) + hover_foreground Transform("gui/button/menubuttons/up.png",xalign=0.5,yalign=0.5,matrixcolor=TintMatrix("#fbff18")) + action Function(swapMods, i, i-1) + else: + add Null(30,30) at truecenter + # Enablin/disabling mods button + button: + at truecenter + style_prefix "main_menu" + action Function(toggle_persistent_mods, i) + activate_sound "audio/ui/snd_ui_click.wav" + add "gui/button/menubuttons/checkbox.png" xalign 0.5 yalign 0.5 + + if persistent.enabled_mods[i][1]: + idle_foreground Transform("gui/button/menubuttons/check.png",xalign=0.5,yalign=0.5,matrixcolor=TintMatrix("#32a852")) + hover_foreground Transform("gui/button/menubuttons/check.png",xalign=0.5,yalign=0.5,matrixcolor=TintMatrix("#fbff18")) + else: + idle_foreground Transform("gui/button/menubuttons/cross.png",xalign=0.5,yalign=0.5,matrixcolor=TintMatrix("#a83232")) + hover_foreground Transform("gui/button/menubuttons/cross.png",xalign=0.5,yalign=0.5,matrixcolor=TintMatrix("#fbff18")) + + # Move mod down button + if i!=len(mod_menu_metadata)-1: + button: + at truecenter + style_prefix "main_menu" + add Null(30,30) + action Function(swapMods, i, i+1) + activate_sound "audio/ui/snd_ui_click.wav" + + idle_foreground Transform("gui/button/menubuttons/down.png",xalign=0.5,yalign=0.5) + hover_foreground Transform("gui/button/menubuttons/down.png",xalign=0.5,yalign=0.5,matrixcolor=TintMatrix("#fbff18")) + + else: + add Null(30,30) at truecenter + + # The main mod button that displays the mod name and potential icon. + python: + mod_button_enabled = (x["Enabled"] == True) and (x.get("Label") != None) + mod_button_alpha = 1.0 if x["Enabled"] == True else 0.4 # Fade mod buttons out if their mod is disabled + + if x['Enabled'] == True and return_translated_metadata(x, "Icon Displayable") != None: + mod_icon = return_translated_metadata(x, "Icon Displayable") + elif return_translated_metadata(x, "Icon") != None: + mod_icon = return_translated_metadata(x, "Icon") + else: + mod_icon = None + + button: + at transform: + truecenter + alpha mod_button_alpha + activate_sound "audio/ui/snd_ui_click.wav" + hovered SetScreenVariable("mod_metadata", x) + + if mod_button_enabled: + action Start(x["Label"]) + else: + action NullAction() + + frame: + xsize 350 + ymaximum 2000 + if mod_button_enabled: + background Frame("gui/button/menubuttons/title_button.png", 12, 12) + hover_background Transform(Frame("gui/button/menubuttons/title_button.png", 12, 12), matrixcolor = BrightnessMatrix(0.1)) + else: + background Transform(Frame("gui/button/menubuttons/title_button.png", 12, 12),matrixcolor=SaturationMatrix(0.5)) + + padding (5, 5) + + # Display mod name and/or icon + if return_translated_metadata(x, "Display") == "icon" and mod_icon != None: + add RoundedCorners(mod_icon, radius=(5, 5, 5, 5)) xsize 342 fit "scale-down" at truecenter + elif return_translated_metadata(x, "Display") == "both" or return_translated_metadata(x, "Display") == None and mod_icon != None: + hbox: + spacing 20 + at truecenter + add mod_icon xysize (100, 100) fit "contain" at truecenter + if mod_button_enabled: + text return_translated_metadata(x, "Name") xalign 0.5 yalign 0.5 size 34 textalign 0.5 at truecenter + else: + text return_translated_metadata(x, "Name") xalign 0.5 yalign 0.5 size 34 textalign 0.5 at truecenter hover_color "#FFFFFF" + else: + if mod_button_enabled: + text return_translated_metadata(x, "Name") xalign 0.5 yalign 0.5 size 34 textalign 0.5 + else: + text return_translated_metadata(x, "Name") xalign 0.5 yalign 0.5 size 34 textalign 0.5 hover_color "#FFFFFF" + + # Only here for backwards compatibility to legacy mods + for x in mod_menu_access: + hbox: + add Null(88) + button: + at truecenter + activate_sound "audio/ui/snd_ui_click.wav" + action Start(x["Label"]) + + frame: + xsize 350 + ymaximum 2000 + background Frame("gui/button/menubuttons/title_button.png", 12, 12) + padding (5, 5) + hover_background Transform(Frame("gui/button/menubuttons/title_button.png", 12, 12), matrixcolor = BrightnessMatrix(0.1)) + text x["Name"] xalign 0.5 yalign 0.5 size 34 textalign 0.5 + else: - xpos 1885-480 - ypos 450 - #use mod_menu_button("gui/button/menubuttons/menu_button.png", _("Return"), ShowMenu("main_menu")) - text _("You have no mods! \nInstall some in:\n\"[moddir]\""): - style_prefix "navigation" - size 45 - text_align 0.5 - xalign 0.5 yalign 0.5 - outlines [(3, "#342F6C", absolute(0), absolute(0))] + fixed: + ymaximum 600 # This is the stupidest fucking hack fix + + if achievement.steamapi: + text _("You have no mods! \nInstall some in:\n\"{color=#abd7ff}[mod_menu_moddir]{/color}\"\nOr download some from the Steam Workshop!"): + style_prefix "navigation" + size 45 + text_align 0.5 + xalign 0.5 yalign 0.5 + outlines [(3, "#342F6C", absolute(0), absolute(0))] + else: + text _("You have no mods! \nInstall some in:\n\"{color=#abd7ff}[mod_menu_moddir]{/color}\""): + style_prefix "navigation" + size 45 + text_align 0.5 + xalign 0.5 yalign 0.5 + outlines [(3, "#342F6C", absolute(0), absolute(0))] + + # Displays the mod metadata on the left side + # This has two seperate viewports for error display because renpy is retarded + if mod_metadata != {}: + viewport: + xmaximum 1190 + ymaximum 930 + xpos 10 + ypos 140 + scrollbars "vertical" + vscrollbar_unscrollable "hide" + mousewheel True + draggable True + vbox: + style_prefix "mod_menu" + + # Thumbnail + python: + if mod_metadata["Enabled"] == True and return_translated_metadata(mod_metadata, "Thumbnail Displayable") != None: + mod_thumbnail = return_translated_metadata(mod_metadata, "Thumbnail Displayable") + elif return_translated_metadata(mod_metadata, "Thumbnail") != None: + mod_thumbnail = return_translated_metadata(mod_metadata, "Thumbnail") + else: + mod_thumbnail = None + + if mod_thumbnail: + frame: + background None + xpadding 30 + bottom_padding 30 + xalign 0.5 + add mod_thumbnail fit 'contain' + + # Mod details + # Omit checking for mod name, since we'll always have some kind of mod name. + # This will also not show anything if there's only a mod name, since we already show one in the mod button. + if return_translated_metadata(mod_metadata, "Version") != None or return_translated_metadata(mod_metadata, "Authors") != None or return_translated_metadata(mod_metadata, "Links") != None: + frame: + background Frame("gui/mod_frame.png", 30, 30) + padding (30, 30) + xfill True + + vbox: + if return_translated_metadata(mod_metadata, "Name") != None: + hbox: + text _("Name: ") + text return_translated_metadata(mod_metadata, "Name") + if return_translated_metadata(mod_metadata, "Version") != None: + hbox: + text _("Version: ") + text return_translated_metadata(mod_metadata, "Version") + if return_translated_metadata(mod_metadata, "Authors") != None: + if isinstance(return_translated_metadata(mod_metadata, "Authors"), list): + hbox: + text _("Authors: ") + text ", ".join(return_translated_metadata(mod_metadata, "Authors")) + else: + hbox: + text _("Author: ") + text return_translated_metadata(mod_metadata, "Authors") + if return_translated_metadata(mod_metadata, "Links") != None: + if isinstance(return_translated_metadata(mod_metadata, "Links"), list): + hbox: + text _("Links: ") + text ", ".join(return_translated_metadata(mod_metadata, "Links")) + else: + hbox: + text _("Link: ") + text return_translated_metadata(mod_metadata, "Links") + + # Description + if return_translated_metadata(mod_metadata, "Description") != None or return_translated_metadata(mod_metadata, "Description") != None: + frame: + background Frame("gui/mod_frame.png", 30, 30) + padding (30, 30) + xfill True + + # If there's no mobile description, display the regular description on Android. + if (not renpy.android or return_translated_metadata(mod_metadata, "Mobile Description") == None) and (return_translated_metadata(x, "Description") != None): + text return_translated_metadata(mod_metadata, "Description") + elif return_translated_metadata(mod_metadata, "Mobile Description") != None: + text return_translated_metadata(mod_metadata, "Mobile Description") + + # Screenshots + python: + if mod_metadata["Enabled"] == True and return_translated_metadata(mod_metadata, "Screenshot Displayables") != None: + mod_screenshot_list = return_translated_metadata(mod_metadata, "Screenshot Displayables") + elif return_translated_metadata(mod_metadata, "Screenshots") != None: + mod_screenshot_list = return_translated_metadata(mod_metadata, "Screenshots") + else: + mod_screenshot_list = None + + if persistent.show_mod_screenshots and mod_screenshot_list: + frame: + background Frame("gui/mod_frame.png", 30, 30) + padding (30, 30) + xfill True + + hbox: + xoffset 12 + box_wrap True + box_wrap_spacing 25 + spacing 25 + + for i in mod_screenshot_list: + imagebutton: + at transform: + ysize 200 subpixel True + fit "scale-down" + xalign 0.5 yalign 0.5 + + idle i + hover Transform(i, matrixcolor=BrightnessMatrix(0.1)) + action Show("mod_screenshot_preview", Dissolve(0.5), img=i) + + elif len(mod_menu_errorcodes) != 0: + viewport: + xmaximum 1190 + ymaximum 920 + xpos 10 + ypos 150 + scrollbars "vertical" + vscrollbar_unscrollable "hide" + mousewheel True + draggable True + vbox: + style_prefix "mod_menu" + frame: + background Frame("gui/mod_frame.png", 30, 30) + padding (30, 30) + xfill True + vbox: + spacing 25 + for t in mod_menu_errorcodes: + text convert_errorcode_to_errorstring(t[0], t[1]) + use extrasnavigation if not persistent.seenModWarning: $ persistent.seenModWarning = True - use OkPrompt(_("Installing mods is dangerous since you are running unknown code in your computer. Only install mods from sources that you trust"), False) + use OkPrompt(_("Installing mods is dangerous since you are running unknown code in your computer. Only install mods from sources that you trust.\n\nIf you have problems with installed mods, check the README.md in the root of the mods folder."), False) -default persistent.seenModWarning = False +style mod_menu_text: + size 34 -############################# -# Stuff for mods in android # -############################# +# A copy of the image previewer found in the phone library, so that the phone library can still be modular if someone takes it out. +screen mod_screenshot_preview(img): + modal True + add Solid("#000") at Transform(alpha=0.6) -init python: - - import os + add img: + align (0.5, 0.5) + fit "scale-down" - if renpy.android and not config.developer: - - moddir = os.path.join(os.environ["ANDROID_PUBLIC"], "game") - - try: - # We have to create both the 'game' and 'mods' folder for android. - os.mkdir(moddir) - os.mkdir(os.path.join(moddir, "mods")) - except: - pass - - else: - moddir = ".../game" - - moddir += "/mods/" \ No newline at end of file + key ["mouseup_1", "mouseup_3"] action Hide("mod_screenshot_preview", dissolve) diff --git a/game/00src/phone/01statements.rpy b/game/00src/phone/01statements.rpy index d0be5ad..328cdc0 100644 --- a/game/00src/phone/01statements.rpy +++ b/game/00src/phone/01statements.rpy @@ -1,6 +1,7 @@ python early in cds_utils: from renpy import store + # if store.is_renpy_version_or_above(7, 6, 0) Lexer = renpy.lexer.Lexer if renpy.version_tuple >= ((7, 6) if renpy.compat.PY2 else (8, 1)) else renpy.parser.Lexer def null_parser(l): @@ -91,7 +92,7 @@ python early in phone._lint: error("audio '{}' isn't loadable.".format(a)) python early in phone: - from renpy.store import cds_utils + from renpy.store import cds_utils class _RawPhoneMessage(cds_utils.Statement): __slots__ = ("sender", "message", "delay") @@ -148,10 +149,12 @@ python early in phone: self.delay = delay def execute(self): - discussion.label(self.label, eval(self.delay, store.__dict__)) + delay = config.default_label_delay if self.delay is None else eval(self.delay, store.__dict__) + discussion.label(self.label, delay) def lint(self): - _lint.eval(self.delay) + if self.delay is not None: + _lint.eval(self.delay) def get_translatable_strings(self): return [self.label] @@ -166,7 +169,8 @@ python early in phone: def execute(self): globals = store.__dict__ - discussion.date(delay=eval(self.delay, globals), auto=eval(self.auto, globals), **{k: eval(v, globals) for k, v in self.kwargs.items()}) + delay = config.default_label_delay if self.delay is None else eval(self.delay, globals) + discussion.date(delay=delay, auto=eval(self.auto, globals), **{k: eval(v, globals) for k, v in self.kwargs.items()}) def lint(self): month = _lint.eval(self.kwargs["month"]) @@ -195,7 +199,8 @@ python early in phone: _lint.error("'{}' isn't a valid second.".format(second)) _lint.eval(self.kwargs["year"]) - _lint.eval(self.delay) + if self.delay is not None: + _lint.eval(self.delay) _lint.eval(self.auto) class _RawPhoneTyping(cds_utils.Statement): @@ -319,7 +324,19 @@ python early in phone: class _RawPhonePass(cds_utils.Statement): def execute(self): - store.pause() + pass + + class _RawPhonePause(cds_utils.Statement): + __slots__ = ("duration",) + + def __init__(self, duration): + self.duration = duration + + def execute(self): + store.pause(eval(self.duration, store.__dict__)) + + def lint(self): + _lint.eval(self.duration) # class _RawPhoneRenpy(cds_utils.Statement): # __slots__ = ("nodes",) @@ -621,12 +638,13 @@ python early in phone: if register: return _RawPhoneRegisterLabel(label) - delay = "0.5" if not ll.keyword("delay") else ll.require(ll.simple_expression) + delay = None if not ll.keyword("delay") else ll.require(ll.simple_expression) return _RawPhoneLabel(label, delay) def _parse_phone_date(ll, register): kwargs = {time_thing: "None" for time_thing in ("month", "day", "year", "hour", "minute", "second")} kwargs["auto"] = "False" + kwargs["delay"] = None seen = set() while True: @@ -652,11 +670,13 @@ python early in phone: break auto = kwargs.pop("auto") + delay = kwargs.pop("delay") if register: + if delay is not None: + renpy.error("'delay' can't be used here") return _RawPhoneRegisterDate(kwargs, auto) - delay = "0.5" if not ll.keyword("delay") else ll.require(ll.simple_expression) return _RawPhoneDate(kwargs, delay, auto) def _parse_phone_typing(ll): @@ -820,6 +840,10 @@ python early in phone: if delay is None: delay = "None" return _RawPhoneAudio(sender, audio, time, delay) + def _parse_phone_pause(ll): + duration = ll.simple_expression() + return _RawPhonePause(duration or "None") + # def _parse_phone_renpy(ll): # ll.require(":") # ll.expect_eol() @@ -863,6 +887,9 @@ python early in phone: elif ll.keyword("pass"): statement = _RawPhonePass() + elif ll.keyword("pause"): + statement = _parse_phone_pause(ll) + # elif ll.keyword("renpy"): # statement = _parse_phone_renpy(ll) @@ -925,9 +952,12 @@ python early in phone: return _RawPhoneDiscussion(gc, statements) - def _predict_phone_discussion(rd): - renpy.predict_screen("phone_message") - return [ ] + # This and the statement that calls this function is stubbed out, otherwise a bug happens where the game crashes when advancing to fast when pulling up the phone + # Not fixed as of EMR Phone 3.2.2 + + #def _predict_phone_discussion(rd): + # renpy.predict_screen("phone_discussion") + # return [ ] def _phone_execute_init(rv): for statement in rv.statements: @@ -942,7 +972,7 @@ python early in phone: execute_init=_phone_execute_init, translation_strings=cds_utils.get_translatable_strings, lint=cds_utils.lint, - predict=_predict_phone_discussion + #predict=_predict_phone_discussion ) ######################################################## @@ -960,20 +990,46 @@ python early in phone: def _parse_phone_call(l): rv = l.require(l.simple_expression) + video = False + nosave = False + + while not l.eol(): + state = l.checkpoint() + thing = l.word() + + if thing == "video": + if video: + l.revert(state) + l.error("video clause already given") + else: + video = True + + elif thing == "nosave": + if nosave: + l.revert(state) + l.error("nosave clause already given") + else: + nosave = True + + else: + l.revert(state) + l.error("unknown property %s" % thing) + + l.expect_eol() l.expect_noblock("phone call") - return rv + return rv, video, nosave - def _execute_phone_call(c): - c = character.character(eval(c, store.__dict__)) - calls.call(c) + def _execute_phone_call(tu): + c = character.character(eval(tu[0], store.__dict__)) + calls.call(c, tu[1], tu[2]) - def _predict_phone_call(c): - renpy.predict_screen("phone_call") + def _predict_phone_call(tu): + renpy.predict_screen("phone_call", video=tu[1]) return [ ] - def _lint_phone_call(c): - _lint.character(c) + def _lint_phone_call(tu): + _lint.character(tu[0]) renpy.register_statement( "phone call", @@ -1047,20 +1103,21 @@ python early in phone: # soooooooooooooooooooooooooooooooooooooooooooooooo clunky class _RawInitPhoneRegister(_RawPhoneRegister): - __slots__ = ("name", "icon", "key", "chars", "default_statement") + __slots__ = ("name", "icon", "key", "chars", "transient", "default_statement") - def __init__(self, gc, statements, name, icon, key, chars, default_statement): + def __init__(self, gc, statements, name, icon, key, chars, transient, default_statement): super(_RawInitPhoneRegister, self).__init__(gc, statements) self.name = name self.icon = icon self.key = key self.chars = chars + self.transient = transient self.default_statement = default_statement def execute(self): if self.default_statement is not None: self.default_statement.execute() - _run_on_start(self._execute, ("phone define gc", self.gc if self.gc is not None else str(self.key))) + execute_default(self._execute, ("phone define gc", self.gc if self.gc is not None else str(self.key))) def _execute(self): gc = None @@ -1070,7 +1127,7 @@ python early in phone: gc = getattr(store, self.default_statement.varname) else: if self.name is not None: - gc = group_chat.GroupChat(self.name, eval(self.icon, globals), eval(self.key, globals)) + gc = group_chat.GroupChat(self.name, eval(self.icon, globals), eval(self.key, globals), transient=self.transient) if gc is not None: for char in self.chars: @@ -1108,6 +1165,7 @@ python early in phone: key = None icon = None chars = [ ] + transient = False _as = None default_statement = None @@ -1144,6 +1202,11 @@ python early in phone: dl.error("character '{}' already given".format(char)) chars.append(char) + elif p == "transient": + if transient: + dl.error("'transient' property already given") + transient = True + elif p == "as": if _as is not None: dl.error("'as' property already given") @@ -1157,19 +1220,20 @@ python early in phone: dl.error("expected 'key' property") if icon is None: - icon = 'phone.config.basedir + "default_icon.png"' + icon = 'phone.asset("default_icon.png")' if _as is not None: filename, linenumber = l.get_location() global _INIT_PHONE_REGISTER_PRIORITY - string = "{init} {_as} = phone.group_chat.GroupChat('{name}', {icon}, {key})" \ + string = "{init} {_as} = phone.group_chat.GroupChat('{name}', {icon}, {key}, transient={transient})" \ .format( init=_INIT_PHONE_REGISTER_PRIORITY, _as=_as, name=name, icon=icon, key=key, + transient=transient, ) lexer = cds_utils.Lexer( @@ -1184,7 +1248,7 @@ python early in phone: if not statements and not no_gc: ll.error("expected at least one statement") - return _RawInitPhoneRegister(gc, statements, name, icon, key, chars, default_statement) + return _RawInitPhoneRegister(gc, statements, name, icon, key, chars, transient, default_statement) def _translation_strings_init_phone_register(ripr): rv = _translation_strings_phone_register(ripr) diff --git a/game/00src/phone/phone/01main.rpy b/game/00src/phone/phone/01main.rpy index 8d37f95..2277e7b 100644 --- a/game/00src/phone/phone/01main.rpy +++ b/game/00src/phone/phone/01main.rpy @@ -2,7 +2,7 @@ python early in phone: from renpy import store from store import config as renpy_config # phone.config is a substore - __version__ = (3, 1, 0) + __version__ = (3, 2, 2) __author__ = "Elckarow#8399" # smh my head my head init -150 python in phone: @@ -26,7 +26,7 @@ init -150 python in phone: import time def format_time(hour, minute): - return time.strftime(__(config.time_format), time.struct_time((1, 1, 1, hour, minute, 0, 1, 0, 0))) + return time.strftime(__(config.time_format), time.gmtime(hour * 3600 + minute * 60)) transform -150 _fits(size): subpixel True xysize (size, size) fit "contain" @@ -98,7 +98,7 @@ init -100 python in phone: menu = not first_call if needs_rollback: - renpy.rollback(True) + renpy.rollback(force=True, greedy=False) return rv @@ -115,6 +115,36 @@ init -100 python in phone: at_list = [at_list] renpy.show_layer_at(at_list=at_list, layer=layer, camera=camera, reset=reset) + + def short_name(s, length): + s = renpy.substitute(s) + if len(s) > length: + s = s[:length - 3] + "..." + + return s + + import os + @renpy.pure + def path_join(*paths): + return os.path.join(*paths).replace("\\", "/") + + @renpy.pure + def asset(path): + return path_join(config.basedir, path) + + def execute_default(f, id): + def run(load): + if id not in _defaults_ran: + _defaults_ran.add(id) + f() + if load: renpy.block_rollback() + + renpy_config.start_callbacks.append(renpy.partial(run, load=False)) + renpy_config.after_load_callbacks.append(renpy.partial(run, load=True)) + +# a set() object +# renamed it because why not +default -999 phone._defaults_ran = phone._id_ran_on_start default -100 phone._stack_depth = 0 default -100 phone._current_screen = None @@ -122,7 +152,7 @@ default broken_phone = False # The base screen for all phone screens. screen _phone(xpos=0.5, xanchor=0.5, ypos=0.05, yanchor=0.0, horizontal=False): - frame style "empty": + frame style "empty" modal True: at transform: subpixel True zoom gui.phone_zoom * (1.12 if horizontal else 1.0) xpos xpos xanchor xanchor @@ -148,7 +178,7 @@ screen _phone(xpos=0.5, xanchor=0.5, ypos=0.05, yanchor=0.0, horizontal=False): fixed style "empty": for o in phone.config.overlay_screens: - use expression o + use expression o # https://www.renpy.org/doc/html/incompatible.html#incompatible-7-5-2 # the thing just above @@ -169,7 +199,7 @@ screen _phone(xpos=0.5, xanchor=0.5, ypos=0.05, yanchor=0.0, horizontal=False): xpos=0.501 ) else: - add phone.config.basedir + "phone_frame.png" at Transform( + add phone.asset("phone_frame.png") at Transform( subpixel=True, align=(0.5, 0.5), rotate=-90 * horizontal, @@ -178,7 +208,7 @@ screen _phone(xpos=0.5, xanchor=0.5, ypos=0.05, yanchor=0.0, horizontal=False): ypos=0.685, xpos=0.501 ) - add phone.config.basedir + "phone_glare.png" at Transform( + add phone.asset("phone_glare.png") at Transform( subpixel=True, align=(0.5, 0.5), rotate=-90 * horizontal, @@ -188,34 +218,10 @@ screen _phone(xpos=0.5, xanchor=0.5, ypos=0.05, yanchor=0.0, horizontal=False): xpos=0.501, alpha=0.1 ) - on "hide" action ( SetVariable("phone.system.at_list", []), ) -# Deprecated stuff -label _phone_register: - $ raise Exception("The label '_phone_register' isn't needed anymore") - -init -500 python in phone: - def register_group_chat(group, *keys): - raise Exception("The 'phone.register_group_chat' function isn't needed anymore") - - def register(f): - raise Exception("The `phone.register` decorator isn't used anymore. (function being decorated: {})".format(f.__name__)) - - def _run_on_start(f, id): - def run(load): - if id not in _id_ran_on_start: - _id_ran_on_start.add(id) - f() - if load: renpy.block_rollback() - - renpy_config.start_callbacks.append(renpy.partial(run, load=False)) - renpy_config.after_load_callbacks.append(renpy.partial(run, load=True)) - -default -1000 phone._id_ran_on_start = set() - init 1500: # narrator is guarenteed to exist at init 1400 # see renpy/common/00definitions.rpy @@ -231,4 +237,11 @@ init 1500: raise Exception("store.PhoneReturn is a reserved name. (value is %r)" % store.PhoneReturn) store.PhoneReturn = PhoneReturn - \ No newline at end of file + if store.is_renpy_version_or_above(7, 6, 0): + renpy_config.detached_layers.append(config.video_call_layer) + + # https://github.com/renpy/renpy/issues/5044 + renpy_config.layer_clipping[config.video_call_layer] = (0, 0, renpy_config.screen_width, renpy_config.screen_height) + +# previous name, when the function it's used in was undocumented +default -1000 phone._id_ran_on_start = set() diff --git a/game/00src/phone/phone/apps/app_base.rpy b/game/00src/phone/phone/apps/app_base.rpy index 7ab0ad9..74c2c24 100644 --- a/game/00src/phone/phone/apps/app_base.rpy +++ b/game/00src/phone/phone/apps/app_base.rpy @@ -5,16 +5,20 @@ screen app_base(action=NullAction()): ysize 50 + (gui.phone_status_bar_height * bool(phone.config.status_bar)) top_padding 10 + (gui.phone_status_bar_height * bool(phone.config.status_bar)) textbutton _("< Back"): + xoffset 20 action (action, PhoneReturn()) sensitive phone.menu - + transclude style app_base_frame is empty: background "#F2F2F2" xfill True xpadding 10 - bottom_padding 10 + # bottom_padding has been changed from 10 and added top_padding to use a hacky way of visually centering the + # top texts of the ui. + bottom_padding 7 + top_padding 3 style app_base_hbox is empty: spacing 5 @@ -23,13 +27,13 @@ style app_base_hbox is empty: style app_base_text is empty: outlines [ ] yalign 0.5 - color "#000" size 19 - font phone.config.basedir + "Aller_Rg.ttf" + color "#000" size 22 + font phone.asset("Aller_Rg.ttf") style app_base_button is empty: yalign 0.5 xalign 0.0 style app_base_button_text is app_base_text: - color "#0094FF" - size 18 \ No newline at end of file + color "#0094FF" + size 22 diff --git a/game/00src/phone/phone/apps/applications.rpy b/game/00src/phone/phone/apps/applications.rpy index 13eee3a..a5fc9d4 100644 --- a/game/00src/phone/phone/apps/applications.rpy +++ b/game/00src/phone/phone/apps/applications.rpy @@ -1,25 +1,18 @@ init -100 python in phone.application: from renpy import store from store import ( - Gradient, RoundedFrame, Transform, + Gradient, RoundedCorners, Transform, Text, phone, NullAction, Fixed, gui, Null, Flatten ) from store.phone import config def IconBackground(d, **kwargs): - return RoundedFrame( + return RoundedCorners( d, radius=gui.phone_application_rounded_corners_radius, xysize=(gui.phone_application_icon_size, gui.phone_application_icon_size), **kwargs ) - def Borders(color, width): - return IconBackground( - Null(gui.phone_application_icon_size, gui.phone_application_icon_size), - outline_color=color, - outline_width=width - ) - def GradientBackground(start_color, end_color, theta=0): return IconBackground(Gradient(start_color, end_color, theta)) @@ -78,7 +71,7 @@ init -100 python in phone.application: Returns `True` if added, `False` if it failed, or `None` if renpy is still init phase and we can't know. """ if renpy.is_init_phase(): - phone._run_on_start(renpy.partial(add_application, app, page, key), ("_phone_add_app", app.name, page, key)) + phone.execute_default(renpy.partial(add_application, app, page, key), ("_phone_add_app", app.name, page, key)) return None if not isinstance(app, Application): @@ -111,7 +104,7 @@ init -100 python in phone.application: def add_app_to_all_characters(app, page=0): if renpy.is_init_phase(): - phone._run_on_start(renpy.partial(add_app_to_all_characters, app, page), ("_phone_add_app", app.name, page)) + phone.execute_default(renpy.partial(add_app_to_all_characters, app, page), ("_phone_add_app", app.name, page)) return None rv = False @@ -141,10 +134,7 @@ screen phone(): current_page = min(current_page, max_page) fixed style_prefix "phone_main": - frame style "empty": - align (1.0, 0.0) padding (10, 10) - background "#474747" - textbutton "Quit" action PhoneReturn() + button style "empty" xysize (1.0, 1.0) action If(coords_to_move is None, PhoneReturn(), SetScreenVariable("coords_to_move", None)) if coords_to_move is not None: key "K_ESCAPE" action SetScreenVariable("coords_to_move", None) @@ -156,7 +146,7 @@ screen phone(): rotate -90 transform_anchor True xysize (150, 30) matrixcolor TintMatrix("#474343ee") action SetScreenVariable("current_page", current_page - 1) - add phone.config.basedir + "arrow_icon.png" + add phone.asset("arrow_icon.png") if current_page != max_page: button: @@ -165,7 +155,7 @@ screen phone(): rotate 90 transform_anchor True xysize (150, 30) matrixcolor TintMatrix("#474343ee") action SetScreenVariable("current_page", current_page + 1) - add phone.config.basedir + "arrow_icon.png" + add phone.asset("arrow_icon.png") use _phone(): style_prefix "phone_main" @@ -187,7 +177,7 @@ screen phone(): vbox: hbox xalign 0.5 spacing 13: for i in range(max_page + 1): - add phone.config.basedir + "circle.png": + add phone.asset("circle.png"): at transform: subpixel True xysize (10, 10) matrixcolor TintMatrix("#4e4e4e") @@ -255,10 +245,7 @@ screen _phone_application_button(app, app_coords, coords_to_move): button: at (_phone_move_application_selected if app_coords == coords_to_move else _phone_move_application) - if app is None: - add phone.application.Borders("#ffffffcc", 5) - else: - add phone.application.IconBackground("#ffffffcc") + add phone.application.IconBackground("#ffffffcc") action [ Function(phone.application.move_application, coords_to_move, app_coords), @@ -298,7 +285,7 @@ style _phone_application_button_vbox is empty: style _phone_application_button_text is empty: text_align 0.5 xalign 0.5 outlines [ ] color "#000" - size 12 font phone.config.basedir + "Metropolis-Regular.otf" + size 12 font phone.asset("Metropolis-Regular.otf") line_spacing 0 style _phone_application_button_fixed is empty: diff --git a/game/00src/phone/phone/apps/calendar/calendar.rpy b/game/00src/phone/phone/apps/calendar/calendar.rpy index 3030210..e679888 100644 --- a/game/00src/phone/phone/apps/calendar/calendar.rpy +++ b/game/00src/phone/phone/apps/calendar/calendar.rpy @@ -110,25 +110,27 @@ init -100 python in phone.calendar: def day_name(year, month, day): return days[calendar.weekday(year, month, day)] - def add_calendar(calendar, key=None): + def add_calendar(year, month, key=None, first_day=SUNDAY): if renpy.is_init_phase(): - phone._run_on_start(renpy.partial(add_calendar, calendar, key), ("_phone_add_calendar", calendar.month, calendar.year, key)) + phone.execute_default(renpy.partial(add_calendar, year=year, month=month, key=key, first_day=first_day), ("_phone_add_calendar", month, year, key)) else: key = character.character(key).key - store.phone.data[key]["calendars"].append(calendar) - def add_calendar_to_all_characters(calendar): + if get_calendar(year=year, month=month, key=key) is not None: + raise Exception("a calendar for the year {} and month {} already exists for the *character* {}" \ + .format(year, month, key)) + + calendars = store.phone.data[key]["calendars"] + calendars.append(Calendar(year=year, month=month, first_day=first_day)) + + calendars.sort(key=lambda c: (c.year, c.month)) + + def add_calendar_to_all_characters(year, month, first_day=SUNDAY): if renpy.is_init_phase(): - phone._run_on_start(renpy.partial(add_calendar_to_all_characters, calendar), ("_phone_add_calendar", calendar.month, calendar.year)) + phone.execute_default(renpy.partial(add_calendar_to_all_characters, year=year, month=month, first_day=first_day), ("_phone_add_calendar", month, year)) else: - l = calendar.lenght(False) - for key in character._characters: - _calendar = Calendar(calendar.month, calendar.year, calendar.firstweekday) - for i in range(l): - _calendar[i].description = calendar[i].description - - add_calendar(_calendar, key) + add_calendar(year=year, month=month, first_day=first_day, key=key) _calendar_button_background = At(config.basedir + "circle.png", store._fits(None)) @@ -139,11 +141,31 @@ init -100 python in phone.calendar: return calendar return None + def _calendar_default_index(m): + n = config.default_calendar_index + + if n is True: + pov_key = store.pov_key + date = system.get_date() + + c = get_calendar(year=date.year, month=date.month, key=pov_key) + + if c is None: + raise Exception("no calendar with the year {} and month {} exists for the *character* {}".format(year, month, pov_key)) + + n = phone.data[pov_key]["calendars"].index(c) + + elif n < 0: + n = m + n + + return n + screen phone_calendars(): default calendars = phone.data[pov_key]["calendars"] default m = len(calendars) - 1 - default n = m + default n = phone.calendar._calendar_default_index(m + 1) default selected_entry = None + default yadj = ui.adjustment() $ calendar = calendars[n] @@ -185,6 +207,7 @@ screen phone_calendars(): SetScreenVariable("selected_entry", entry), SetScreenVariable("selected_entry", None) ), + Function(yadj.change, 0), SelectedIf(selected_entry is entry) ) @@ -202,15 +225,16 @@ screen phone_calendars(): side "l c r" xalign 0.5: textbutton "<" action If(n != 0, (SetScreenVariable("n", n - 1), SetScreenVariable("selected_entry", None))) - text _("[calendar.month_name]-[calendar.year]") size 25 xalign 0.5 text_align 0.5 + text _("[calendar.month_name!t]-[calendar.year]") size 25 xalign 0.5 text_align 0.5 textbutton ">" action If(n != m, (SetScreenVariable("n", n + 1), SetScreenVariable("selected_entry", None))) - frame: + frame at CurriedRoundedCorners(radius=(0, 25, 0, 25)): vbox spacing 3 at Flatten: text _("Notes:") size 22 viewport: draggable True mousewheel True + yadjustment yadj if selected_entry is not None: if selected_entry.description is not None: @@ -232,7 +256,7 @@ style phone_calendar_button_text is empty: outlines [] size 16 align (0.5, 0.5) text_align 0.5 - font phone.config.basedir + "Aller_Rg.ttf" + font phone.asset("Aller_Rg.ttf") style phone_calendar_button_text_special is phone_calendar_button_text: size 12 @@ -254,7 +278,7 @@ style phone_calendar_notes_button_text is phone_calendar_notes_text: size 21 style phone_calendar_notes_frame is empty: - background RoundedFrame("#e0e0e0", radius=(0, 25, 0, 25)) + background "#e0e0e0" padding (13, 7, 13, 0) xfill True ysize 180 diff --git a/game/00src/phone/phone/apps/call_history/call_history.rpy b/game/00src/phone/phone/apps/call_history/call_history.rpy index 8552697..ea7f480 100644 --- a/game/00src/phone/phone/apps/call_history/call_history.rpy +++ b/game/00src/phone/phone/apps/call_history/call_history.rpy @@ -35,7 +35,7 @@ screen phone_call_history(): fixed: hbox style "empty" yalign 0.2 spacing 10: - text char.short_name + text phone.short_name(char.name, 26) if entry.duration is not None: text "-" text entry._duration_to_str() diff --git a/game/00src/phone/phone/apps/calls/call_functions.rpy b/game/00src/phone/phone/apps/calls/call_functions.rpy index 9524443..aabd7d9 100644 --- a/game/00src/phone/phone/apps/calls/call_functions.rpy +++ b/game/00src/phone/phone/apps/calls/call_functions.rpy @@ -16,11 +16,10 @@ init -100 python in phone.calls: from store.phone import character, config, show_layer_at, set_current_screen, system import time - # config.layer_at_transforms["phone_call"] = call_blur + # config.layer_at_transforms["phone_call"] = Transform(matrixcolor=BrightnessMatrix(-0.21), blur=20) # config.hide_status_bar_screens.append("phone_call") - def call(caller): - + def call(caller, video=False, nosave=False): global _current_caller if _current_caller is not None: raise Exception("can't have 2 phone calls at the same time") @@ -28,13 +27,16 @@ init -100 python in phone.calls: # store.narrator = store._phone_narrator store._window_hide() + set_current_screen("phone_call") show_layer_at([call_blur]) - renpy.show_screen("phone_call") - renpy.with_statement(config.enter_transition) + renpy.show_screen("phone_call", video=video) + renpy.with_statement(config.enter_transition) store._window_auto = True - + + global _nosave + _nosave = bool(nosave) def end_call(): store._window_hide() @@ -50,9 +52,11 @@ init -100 python in phone.calls: show_layer_at([call_unblur], reset=True) renpy.hide_screen("phone_call") + if store.is_renpy_version_or_above(7, 5, 0): + renpy.scene(config.video_call_layer) renpy.with_statement(config.exit_transition) - set_current_screen(None) + set_current_screen(None) store._window_auto = True class _CallEntry(object): @@ -71,14 +75,19 @@ init -100 python in phone.calls: date = system.get_date() - ch1 = phone.data[key1]["call_history"] - ch1.append(_CallEntry(key2, date, duration)) + global _nosave + if _nosave is not None and not _nosave: + _nosave = None + + ch1 = phone.data[key1]["call_history"] + ch1.append(_CallEntry(key2, date, duration)) - while len(ch1) > config.call_history_lenght: ch1.pop(0) - - ch2 = phone.data[key2]["call_history"] - ch2.append(_CallEntry(key1, date, duration)) + while len(ch1) > config.call_history_lenght: ch1.pop(0) - while len(ch2) > config.call_history_lenght: ch2.pop(0) + ch2 = phone.data[key2]["call_history"] + ch2.append(_CallEntry(key1, date, duration)) -default -100 phone.calls._current_caller = None \ No newline at end of file + while len(ch2) > config.call_history_lenght: ch2.pop(0) + +default -100 phone.calls._current_caller = None +default -100 phone.calls._nosave = None diff --git a/game/00src/phone/phone/apps/calls/call_screen.rpy b/game/00src/phone/phone/apps/calls/call_screen.rpy index 8785205..c4584ec 100644 --- a/game/00src/phone/phone/apps/calls/call_screen.rpy +++ b/game/00src/phone/phone/apps/calls/call_screen.rpy @@ -6,28 +6,35 @@ init -100 python in phone.calls: default phone.calls._call_time_st = 0.0 -screen phone_call(): - use _phone(): - style_prefix "phone_call" - +screen phone_call(video=False): + use _phone(xpos=gui.phone_call_xpos, xanchor=0.0): add Solid("#302D29") - vbox: - text phone.calls._current_caller.name alt "" - add DynamicDisplayable(phone.calls._call_time) alt "" + if video and is_renpy_version_or_above(7, 6, 0): # _phone_video_call uses the `Layer` displayable + use _phone_video_call() + else: + use _phone_call() + + #if not phone.config.quick_menu and quick_menu: + # use quick_menu() - frame: - add phone.calls._current_caller.icon at _fits(None) +screen _phone_call(): + style_prefix "phone_call" - if phone.config.quick_menu and quick_menu: + vbox: + text phone.short_name(phone.calls._current_caller.name, 12) alt "" + add DynamicDisplayable(phone.calls._call_time) alt "" + + frame: + add phone.calls._current_caller.icon at _fits(None) + + if phone.config.quick_menu and quick_menu: + frame style "empty" xalign 0.5 ypos 0.45: use phone_quick_menu() - add phone.config.basedir + "hang_up.png": - subpixel True zoom 0.35 - xalign 0.5 ypos 0.8 - - # if not phone.config.quick_menu and quick_menu: - # use quick_menu() + add phone.asset("hang_up.png"): + subpixel True xysize (63, 63) + xalign 0.5 ypos 0.8 style phone_call_vbox is empty: spacing 3 @@ -40,7 +47,7 @@ style phone_call_text is empty: outlines [ ] line_spacing 0 size 24 - font phone.config.basedir + "Aller_Rg.ttf" + font phone.asset("Aller_Rg.ttf") hyperlink_functions hyperlink_functions_style("phone_call_text_hyperlink") style phone_call_text_hyperlink is phone_call_text: @@ -54,4 +61,22 @@ style phone_call_frame is empty: xysize ( 120, 120) # :) padding (int(22 * (120 / 404)), int(22 * (120 / 404))) ypos 0.18 - xalign 0.5 \ No newline at end of file + xalign 0.5 + +screen _phone_video_call(): + style_prefix "phone_video_call" + + add Layer(phone.config.video_call_layer): + at Transform(**phone.config.video_call_layer_transform_properties) + + vbox: + text _("Facetime - [phone.calls._current_caller.name!t]") + add DynamicDisplayable(phone.calls._call_time) + + use phone_quick_menu_video() + +style phone_video_call_vbox is phone_call_vbox: + ypos 0.03 + +style phone_video_call_text is phone_call_text: + size 27 diff --git a/game/00src/phone/phone/apps/calls/quick_menu.rpy b/game/00src/phone/phone/apps/calls/quick_menu.rpy index ccc51a6..a1d2491 100644 --- a/game/00src/phone/phone/apps/calls/quick_menu.rpy +++ b/game/00src/phone/phone/apps/calls/quick_menu.rpy @@ -1,53 +1,50 @@ screen phone_quick_menu(): - grid 3 2: - style_prefix "phone_quick_menu" - xalign 0.5 ypos 0.45 - + grid 3 2 style_prefix "phone_quick_menu": vbox: imagebutton: - idle phone.config.basedir + "quick_menu_history_idle.png" - hover phone.config.basedir + "quick_menu_history_selected.png" - selected_idle phone.config.basedir + "quick_menu_history_selected.png" + idle phone.asset("quick_menu_history_idle.png") + hover phone.asset("quick_menu_history_selected.png") + selected_idle phone.asset("quick_menu_history_selected.png") action ShowMenu("history") text _("History") vbox: imagebutton: - idle phone.config.basedir + "quick_menu_afm_idle.png" - hover phone.config.basedir + "quick_menu_afm_selected.png" - selected_idle phone.config.basedir + "quick_menu_afm_selected.png" + idle phone.asset("quick_menu_afm_idle.png") + hover phone.asset("quick_menu_afm_selected.png") + selected_idle phone.asset("quick_menu_afm_selected.png") action Preference("auto-forward", "toggle") text _("Auto") vbox: imagebutton: - idle phone.config.basedir + "quick_menu_skip_idle.png" - hover phone.config.basedir + "quick_menu_skip_selected.png" - selected_idle phone.config.basedir + "quick_menu_skip_selected.png" + idle phone.asset("quick_menu_skip_idle.png") + hover phone.asset("quick_menu_skip_selected.png") + selected_idle phone.asset("quick_menu_skip_selected.png") action Skip() text _("Skip") vbox: imagebutton: - idle phone.config.basedir + "quick_menu_settings_idle.png" - hover phone.config.basedir + "quick_menu_settings_selected.png" - selected_idle phone.config.basedir + "quick_menu_settings_selected.png" + idle phone.asset("quick_menu_settings_idle.png") + hover phone.asset("quick_menu_settings_selected.png") + selected_idle phone.asset("quick_menu_settings_selected.png") action ShowMenu("preferences") text _("Settings") vbox: imagebutton: - idle phone.config.basedir + "quick_menu_save_idle.png" - hover phone.config.basedir + "quick_menu_save_selected.png" - selected_idle phone.config.basedir + "quick_menu_save_selected.png" + idle phone.asset("quick_menu_save_idle.png") + hover phone.asset("quick_menu_save_selected.png") + selected_idle phone.asset("quick_menu_save_selected.png") action ShowMenu("save") text _("Save") vbox: imagebutton: - idle phone.config.basedir + "quick_menu_load_idle.png" - hover phone.config.basedir + "quick_menu_load_selected.png" - selected_idle phone.config.basedir + "quick_menu_load_selected.png" + idle phone.asset("quick_menu_load_idle.png") + hover phone.asset("quick_menu_load_selected.png") + selected_idle phone.asset("quick_menu_load_selected.png") action ShowMenu("load") text _("Load") @@ -56,4 +53,47 @@ style phone_quick_menu_grid is empty: style phone_quick_menu_vbox is empty style phone_quick_menu_text is phone_call_time: - size 14 \ No newline at end of file + size 14 + +screen phone_quick_menu_video(): + default qm = False + default anim_time = 0.35 + + showif qm: + add "#000": + at transform: + alpha 0.0 + on show: + ease anim_time alpha 0.35 + on hide: + ease anim_time alpha 0.0 + + vbox style "empty" yalign 1.0 xsize 1.0 xfill True: + button style "empty" padding (5, 7, 5, 4) xalign 0.5: + at transform: + ease anim_time matrixtransform RotateMatrix(0, 0, 180 * qm) matrixcolor OpacityMatrix(0.8 if qm else 0.6) + + action ToggleLocalVariable("qm") + + add phone.asset("arrow_icon.png"): + at transform: + subpixel True xysize (70, 18) + + showif qm: + frame style "empty" top_padding 30 bottom_padding 15 xsize 1.0 modal True: + at transform: + subpixel True crop (0, 0, 1.0, 0.0) + on show: + ease anim_time crop (0, 0, 1.0, 1.0) alpha 1.0 + on hide: + ease anim_time crop (0, 0, 1.0, 0.0) alpha 0.0 + + background "#00000060" + + vbox style "empty" xalign 0.5: + use phone_quick_menu() + + null height 15 + + add phone.asset("hang_up.png"): + subpixel True xysize (63, 63) xalign 0.5 diff --git a/game/00src/phone/phone/apps/calls/say.rpy b/game/00src/phone/phone/apps/calls/say.rpy index c13eceb..58dfa4b 100644 --- a/game/00src/phone/phone/apps/calls/say.rpy +++ b/game/00src/phone/phone/apps/calls/say.rpy @@ -30,8 +30,8 @@ style phone_say_dialogue is empty: ypos 0.0 outlines [ ] color "#fff" - font "gui/FallingSky.otf" + font phone.asset("FallingSky.otf") style phone_say_label is phone_say_dialogue: - font phone.config.basedir + "JetBrainsMono-ExtraBold.ttf" + font phone.asset("FallingSky.otf") size 27 \ No newline at end of file diff --git a/game/00src/phone/phone/apps/contacts/contacts.rpy b/game/00src/phone/phone/apps/contacts/contacts.rpy index 9a6e341..0182121 100644 --- a/game/00src/phone/phone/apps/contacts/contacts.rpy +++ b/game/00src/phone/phone/apps/contacts/contacts.rpy @@ -34,21 +34,20 @@ screen phone_contacts(): PhoneMenu("phone_discussion") ) - $ sender, message = group_chat._get_display_last_message() - hbox: add group_chat.icon at _fits(46) yalign 0.5 fixed: - text message yalign 0.2: + text phone.short_name(group_chat.name, 26) yalign 0.2: if group_chat.unread: color "#000" - - text _("The [group_chat._date_text] at [group_chat._hour_text]"): - style "phone_contacts_date_text" yalign 1.0 - if sender is not None: - text sender.short_name style "phone_contacts_date_text" align (1.0, 1.0) + text ( + _("The [group_chat._date_text] at [group_chat._hour_text]") + if group_chat.number_of_messages != 0 + else _("Empty group chat") + ): + style "phone_contacts_date_text" yalign 1.0 style phone_contacts_side is empty: xfill True @@ -74,7 +73,7 @@ style phone_contacts_text is empty: outlines [ ] color "#525252" size 18 - font phone.config.basedir + "Aller_Rg.ttf" + font phone.asset("Aller_Rg.ttf") style phone_contacts_no_friends is phone_contacts_text: color "#000" diff --git a/game/00src/phone/phone/apps/discussion/discussion.rpy b/game/00src/phone/phone/apps/discussion/discussion.rpy index 856f6d0..1d28aa9 100644 --- a/game/00src/phone/phone/apps/discussion/discussion.rpy +++ b/game/00src/phone/phone/apps/discussion/discussion.rpy @@ -31,20 +31,19 @@ init -100 python in phone.discussion: return else: raise Exception("group chat not given (no previous group chat was found)") - - if _group_chat is not None: - raise Exception("preparing group chat while another convesation is going on") gc = group_chat(gc) if not gc._characters: raise Exception("group chat '{}' has no characters".format(gc.name)) - _group_chat = gc _group_chat.unread = False store._window_hide() + + _yadjustment.value = float("inf") + set_current_screen("phone_discussion") show_layer_at([disc_blur]) renpy.show_screen("phone_discussion") @@ -53,23 +52,25 @@ init -100 python in phone.discussion: store._window_auto = True def end_discussion(): - store._window_hide() - global _group_chat - if _group_chat is None: - raise Exception("ending discussion, but no discussion ever started") + if _group_chat is None: return + + store._window_hide() for key in _group_chat._characters: sort_messages(key) - if _group_chat.ephemeral: - _group_chat.clear_payload() + + if _group_chat.transient: + _group_chat.clear() + _group_chat = None show_layer_at([disc_unblur], reset=True) renpy.hide_screen("phone_discussion") renpy.with_statement(config.exit_transition) + set_current_screen(None) - + store._window_auto = True class _Payload(object): @@ -120,7 +121,6 @@ init -100 python in phone.discussion: if _group_chat._page == 0: _yadjustment.value = float("inf") - _current_payload = None if not no_pause: @@ -144,7 +144,6 @@ init -100 python in phone.discussion: dc.send(delay) - def register_message(group, sender, text): _check_for_tags(text) @@ -299,6 +298,10 @@ init -100 python in phone.discussion: p = _Payload(sender.key, audio, AUDIO) group._save_payload(p) _run_callbacks(group, "save", p) + + _yadjustment = ui.adjustment() + + def add_history(sender,text): @@ -340,8 +343,6 @@ init -100 python in phone.discussion: while len(history) > history_length: history.pop(0) - - _yadjustment = ui.adjustment() default -100 phone.discussion._current_payload = None default -100 phone.discussion._group_chat = None @@ -374,7 +375,7 @@ init python in phone.discussion: from store.phone.character import character init 1400 python in phone: - @renpy.partial(_run_on_start, id="__sort_register_messages") + @renpy.partial(execute_default, id="__sort_register_messages") def __sort_register_messages(): global data for key in data: diff --git a/game/00src/phone/phone/apps/discussion/discussion_screens.rpy b/game/00src/phone/phone/apps/discussion/discussion_screens.rpy index 05629ae..a618c95 100644 --- a/game/00src/phone/phone/apps/discussion/discussion_screens.rpy +++ b/game/00src/phone/phone/apps/discussion/discussion_screens.rpy @@ -1,13 +1,13 @@ screen phone_discussion(): - key ["mouseup_1", "mouseup_3"] action Function(renpy.end_interaction, True) capture False use _phone(): + key ["mouseup_1", "mouseup_3", "K_UP"] action Function(renpy.end_interaction, True) capture True side "t b c": use app_base(action=(SetField(phone.discussion, "_group_chat", None), Function(phone.discussion.audio_messages.reset))): style_prefix "app_base" hbox: add phone.discussion._group_chat.icon at _fits(36) yalign 0.5 - text phone.discussion._group_chat.short_name alt "" + text phone.short_name(phone.discussion._group_chat.name, 9) alt "" use _chat_textbox() use _chat_messages() @@ -17,6 +17,7 @@ init -100 python in phone.discussion: class _AutoScrollVP(renpy.display.layout.Container): """ Actual dark magic... + filtered, not even joking """ def __init__(self, vp): super(_AutoScrollVP, self).__init__(vp) @@ -34,7 +35,6 @@ init -100 python in phone.discussion: adjusted_st, _ = self.adj_text.adjusted_times() delta = min(1.0, adjusted_st / text_time) - # not really its size, but the width of what has been rendered # | | # | This is a super dup | @@ -49,27 +49,31 @@ init -100 python in phone.discussion: return super(_AutoScrollVP, self).render(w, h, st, at) screen _chat_textbox(): + style_prefix "phone_textbox" + frame: # ugly double framing but whatever, we're rolling with it otherwise it make the entire phone stretch outwards. + top_margin 10 # Part of the revised phone ui. Needed to avoid clipping + hbox: + frame: + style "phone_text_inputbox" + if phone.discussion._current_payload is not None and phone.discussion._current_payload.source is not None: + $ sender = phone.character.character(phone.discussion._current_payload.source) + if ( + sender.is_pov and + phone.discussion._current_payload.type == phone.discussion._PayloadTypes.TEXT + ): + fixed style "empty": + yfill True + xsize 0.9 - frame: - side "l r": - if phone.discussion._current_payload is not None and phone.discussion._current_payload.source is not None: - $ sender = phone.character.character(phone.discussion._current_payload.source) - if ( - sender.is_pov and - phone.discussion._current_payload.type == phone.discussion._PayloadTypes.TEXT - ): - fixed style "empty": - yfill True - xsize 0.9 + viewport at phone.discussion._AutoScrollVP style "empty": + draggable False + mousewheel False + yalign 0.5 + xoffset 11 #don't add it to Text() - viewport at phone.discussion._AutoScrollVP style "empty": - draggable False - mousewheel False - yalign 0.5 - - frame style "phone_textbox_typing_text_frame": - add renpy.display.layout.AdjustTimes( + frame style "phone_textbox_typing_text_frame": + add renpy.display.layout.AdjustTimes( Text( " " + __(phone.discussion._current_payload.data), style="phone_textbox_text", @@ -78,18 +82,33 @@ screen _chat_textbox(): None, None ) + else: + text _("Type a message.") color "#666" alt "" xoffset 11 + # ugly ahh + # to the genius who tried removing the top conditional, no, the phone creator did this hack for a reason + # you gargoyle else: - text _(" Type a message.") color "#666" alt "" - # ugly ahh - else: - text _(" Type a message.") color "#666" alt "" + text _("Type a message.") color "#666" alt "" xoffset 11 - text _("Send ") color "#0094FF" xalign 1.0 alt "" - + frame: + style "phone_text_sendbutton" + text _("Send") color "#0094FF" xalign 0.0 alt "" + + +style phone_text_inputbox is empty: + ysize 52 xsize 0.875 + padding (10, 5, 0) + background RoundedCorners(Solid("#F2F2F2"), radius=phone.config.textbox_radius, outline_width=2.0, outline_color="#9b9b9b") + +style phone_text_sendbutton is empty: + padding (8, 10, 0) + ysize 50 style phone_textbox_frame is empty: ysize 50 xfill True - background "#F2F2F2" + xsize 0.94 + xoffset 12 + yoffset -20 padding (10, 10) style phone_textbox_side is empty: @@ -98,11 +117,11 @@ style phone_textbox_side is empty: style phone_textbox_text is empty: outlines [ ] - size 16 + size 22 #unbearable to read at 18 color "#000" line_leading 0 line_spacing 0 - font phone.config.basedir + "Aller_Rg.ttf" + font phone.asset("Aller_Rg.ttf") yalign 0.5 layout "nobreak" @@ -110,6 +129,8 @@ style phone_textbox_typing_text_frame is empty: yfill True + + screen _chat_message(p): style_prefix "phone_messages" @@ -140,6 +161,7 @@ screen _chat_messages(): viewport style "empty" at Flatten: yadjustment phone.discussion._yadjustment + ymaximum 715 # Part of the revised phone ui. Needed to make room for the margin property in the textbox. draggable True mousewheel True yinitial 1.0 @@ -148,7 +170,7 @@ screen _chat_messages(): frame style "empty": yalign 1.0 - padding (10, 10) + padding (10, 20, 10, 40) vbox: if phone.discussion._group_chat._can_load_more(): @@ -156,7 +178,6 @@ screen _chat_messages(): action ( Function(phone.discussion._group_chat._page_up), SetField(phone.discussion._yadjustment, "value", float("inf")), - SetLocalVariable("_label", False), Function(phone.discussion.audio_messages.reset) ) bottom_margin 5 @@ -169,13 +190,12 @@ screen _chat_messages(): null height gui.phone_message_label_null_height text p.data style "phone_messages_text_label" - else: if _label: $ _label = False null height gui.phone_message_label_null_height - if p.type == phone.discussion._PayloadTypes.TEXT: + if p.type == phone.discussion._PayloadTypes.TEXT: use _chat_message(p): if i != phone.discussion._group_chat.number_of_messages-1 or renpy.get_screen("say"): text p.data alt "" @@ -184,7 +204,6 @@ screen _chat_messages(): text p.data alt sender + ": " + p.data # $print(dir(phone.discussion._group_chat)) - elif p.type == phone.discussion._PayloadTypes.IMAGE: use _chat_message(p): imagebutton: @@ -193,7 +212,7 @@ screen _chat_messages(): fit "scale-down" idle p.data - action Show("_phone_image", dissolve, img=p.data) + action Show("_phone_image", Dissolve(0.5), img=p.data) elif p.type == phone.discussion._PayloadTypes.AUDIO: use _chat_message(p): @@ -202,7 +221,7 @@ screen _chat_messages(): add DynamicDisplayable(phone.discussion.audio_messages.button_image, p=p) action Function(phone.discussion.audio_messages.play_audio, p, p.data), - add phone.config.basedir + "audio_message_wave_icon.png": + add phone.asset("audio_message_wave_icon.png"): at _fits(None), phone.discussion.audio_messages.AudioWave(p) else: @@ -214,15 +233,17 @@ screen _chat_messages(): if _label: $ _label = False null height gui.phone_message_label_null_height - + for i, caption in enumerate(phone.discussion._current_payload.data): - textbutton caption: - style_prefix "phone_messages_choice" - action Return(caption) - at transform: + frame style "empty": + at transform: # support for versions that can't have `at` and `at transform` at the same time subpixel True alpha 0.0 xoffset -20 xalign 1.0 i / 9 ease_quad 0.35 alpha 1.0 xoffset 0 + + textbutton caption at CurriedRoundedCorners(radius=phone.config.textbox_radius): + style_prefix "phone_messages_choice" + action Return(caption) else: $ sender = phone.character.character(phone.discussion._current_payload.source) @@ -234,7 +255,6 @@ screen _chat_messages(): if phone.discussion._current_payload.type == phone.discussion._PayloadTypes.IMAGE: use _chat_message(phone.discussion._current_payload): text _("{u}{i}Loading Image...{i}{/u}") - elif phone.discussion._current_payload.type == phone.discussion._PayloadTypes.AUDIO: use _chat_message(phone.discussion._current_payload): text _("{u}{i}Loading Audio...{i}{/u}") @@ -243,19 +263,17 @@ screen _chat_messages(): use _chat_message(phone.discussion._current_payload): text _("{u}{i}Loading Video...{i}{/u}") - else: + else: if _label: $ _label = False null height gui.phone_message_label_null_height use _phone_message_typing(sender) - if phone.discussion._group_chat._page > 0: textbutton _("Go Back"): action ( Function(phone.discussion._group_chat._page_down), SetField(phone.discussion._yadjustment, "value", 0.0), - SetLocalVariable("_label", False), Function(phone.discussion.audio_messages.reset) ) top_margin 5 @@ -267,7 +285,7 @@ screen _phone_image(img): add img: align (0.5, 0.5) - key ["mouseup_1", "mouseup_3"] action Hide("_phone_image", dissolve) + key ["mouseup_1", "mouseup_3"] action Hide("_phone_image", Dissolve(0.5)) style phone_messages_button is empty: xalign 0.5 @@ -276,24 +294,24 @@ style phone_messages_button_text is phone_say_dialogue: xalign 0.5 text_align 0.5 ypos 0.0 - size 16 + size 22 color "#000" style phone_messages_vbox is empty: spacing 5 yalign 1.0 - xfill True + xfill True style phone_messages_hbox is empty: spacing 5 style phone_messages_text is empty: outlines [ ] - size 22 + size 24 line_leading 0 line_spacing 0 layout "greedy" - font phone.config.basedir + "Aller_Rg.ttf" + font phone.asset("Aller_Rg.ttf") hyperlink_functions hyperlink_functions_style("phone_messages_text_hyperlink") style phone_messages_text is empty: @@ -310,19 +328,19 @@ style phone_messages_frame is empty: style phone_messages_text_label is phone_messages_text: color "#000" xalign 0.5 text_align 0.5 - size 15 + size 20 xsize 0.8 style phone_messages_choice_button is phone_messages_frame: - background RoundedFrame(Solid("#eeeeee"), radius=phone.config.textbox_radius, outline_width=2.0, outline_color="#9b9b9b") - hover_background RoundedFrame(Solid("#b4b4b4"), radius=phone.config.textbox_radius, outline_width=2.0, outline_color="#4d4d4d") + background "#eeeeee" + hover_background "#b4b4b4" padding (12, 8) style phone_messages_choice_button_text is phone_messages_text: color "#000" outlines [ ] yalign 0.5 - size 18 + size 22 layout "tex" style phone_messages_audio_hbox is empty: diff --git a/game/00src/phone/phone/apps/discussion/group_chats.rpy b/game/00src/phone/phone/apps/discussion/group_chats.rpy index 1193913..2146de9 100644 --- a/game/00src/phone/phone/apps/discussion/group_chats.rpy +++ b/game/00src/phone/phone/apps/discussion/group_chats.rpy @@ -1,28 +1,23 @@ init -100 python in phone.group_chat: - from renpy import store - from store import __, phone + from renpy.store import store, phone import datetime - # The max lenght of a *group chat*'s name shortened. - short_name_length = 9 - - # How many messages we display at the same time. - messages_displayed = 175 - - # If the next "load" of messages contains this many or less messages, add those messages to the current load. - messages_fill_if_lower = 15 + config = phone.config class GroupChat(object): - def __init__(self, name, icon, key, ephemeral=False): + transient = False + _unread = True + + def __init__(self, name, icon, key, transient=False): global _group_chats _group_chats[key] = self self.name = name self.icon = icon self.date = datetime.datetime(year=1970, month=1, day=1, hour=0, minute=0) - self.ephemeral = ephemeral + self.transient = transient - self.unread = True + self._unread = True self._characters = set() self._payloads = [ ] @@ -30,16 +25,23 @@ init -100 python in phone.group_chat: if key is None: raise ValueError("key may not be 'None'") self.key = key + + # deprecated + self.short_name = name @property - def short_name(self): - global short_name_length - name = __(self.name) - if len(name) > short_name_length: - name = name[:short_name_length - 3] + "..." - - return name + def unread(self): + if not config.unread_group_chat_pov: + return self._unread + return phone.data[store.pov_key]["group_chat_unread_pov"].setdefault(self.key, True) + @unread.setter + def unread(self, v): + if not config.unread_group_chat_pov: + self._unread = v + else: + phone.data[store.pov_key]["group_chat_unread_pov"][self.key] = v + def add_character(self, char): if isinstance(char, list): for c in char: @@ -74,67 +76,27 @@ init -100 python in phone.group_chat: @property def number_of_messages(self): return self.number_of_messages_sent(None) + + def clear(self): + self._payloads.clear() def _can_load_more(self): if not self._payloads: return False - return next(self._get_messages()) is not self._payloads[0] - - def _get_display_last_message(self): - italic = True - - if not self._payloads: - sender = None - message = __("Empty group chat") - - else: - p = self._payloads[-1] - - sender = p.source - if sender is not None: sender = character(sender) - - _type = p.type - - if _type == _PayloadTypes.TEXT: - message = renpy.substitute(p.data) - italic = False - - elif _type == _PayloadTypes.IMAGE: - message = __("Image sent") - - elif _type == _PayloadTypes.LABEL: - message = renpy.substitute(p.data) - - elif _type == _PayloadTypes.DATE: - message = p.data - - elif _type == _PayloadTypes.AUDIO: - message = __("Audio sent") - - elif _type == _PayloadTypes.VIDEO: - message = __("Video sent") - - message = remove_text_tags(message) - - LIMIT = 27 - if len(message) >= LIMIT: - message = message[:LIMIT - 3] + "..." - - if italic: - message = "{i}" + message + "{/i}" - - return (sender, message) + return self._get_messages()[0] is not self._payloads[0] def _get_messages(self): - global messages_displayed + messages_displayed = config.messages_displayed + min_x = self._page * messages_displayed max_x = min_x + messages_displayed - global messages_fill_if_lower - remaining = len(self._payloads) - max_x - if remaining <= messages_fill_if_lower: + l = len(self._payloads) + + remaining = l - max_x + if remaining <= config.messages_fill_if_lower: max_x += remaining - return reversed(self._payloads[::-1][min_x:max_x]) + return self._payloads[l-max_x:l-min_x] @property def _date_text(self): @@ -171,9 +133,6 @@ init -100 python in phone.group_chat: def __len__(self): return len(self._payloads) - def clear_payload(self): - self._payloads=[] - def __hash__(self): return hash(self.key) diff --git a/game/00src/phone/phone/apps/discussion/typing.rpy b/game/00src/phone/phone/apps/discussion/typing.rpy index d407f7a..ff7d862 100644 --- a/game/00src/phone/phone/apps/discussion/typing.rpy +++ b/game/00src/phone/phone/apps/discussion/typing.rpy @@ -19,7 +19,7 @@ screen _phone_message_typing(sender): text "⚫" at _phone_message_typing(0.2) alt "" text "⚫" at _phone_message_typing(0.4) alt "" - text _("[sender.short_name!t] is typing...") style "phone_typing_istyping" yalign 0.5 alt "" + text __("{short_name} is typing...").format(short_name=phone.short_name(sender.name, 9)) style "phone_typing_istyping" yalign 0.5 alt "" style phone_typing_hbox is empty: spacing 3 @@ -34,5 +34,9 @@ style phone_typing_text is phone_messages_text: style phone_typing_istyping is empty: color "#626262" outlines [ ] - size 16 - font phone.config.basedir + "Aller_Rg.ttf" \ No newline at end of file + size 22 + font phone.asset("Aller_Rg.ttf") + +init python: + if is_renpy_version_or_above(7, 7, 0): + style.phone_typing_text.emoji_font = None diff --git a/game/00src/phone/phone/characters.rpy b/game/00src/phone/phone/characters.rpy index d05d0bd..85aa567 100644 --- a/game/00src/phone/phone/characters.rpy +++ b/game/00src/phone/phone/characters.rpy @@ -1,17 +1,9 @@ init -100 python in phone.character: - from renpy import store - from store import Color, __, RoundedFrame + from renpy.store import store, Color, __, RoundedCorners, phone from store.phone import config - # The max lenght of a *character*'s name shortened. - character_short_name_length = 16 - - # A number of seconds added to the pause before each message. - message_delay = 0.6 - class Character(object): def __init__(self, name, icon, key, cps, color): - global _characters _characters[key] = self self.name = name @@ -23,26 +15,19 @@ init -100 python in phone.character: if key is None: raise ValueError("key may not be 'None'") self.key = key + # deprecated + self.short_name = name + def get_textbox(self): return get_textbox(self.color) - - @property - def short_name(self): - global character_short_name_length - name = __(self.name) - if len(name) > character_short_name_length: - name = name[:character_short_name_length - 3] + "..." - return name - @property def is_pov(self): return self.key == store.pov_key - def get_typing_delay(self, message, substitute=True): - global message_delay - if substitute: message = renpy.substitute(message) - return (len(message) / self.cps) + message_delay + def get_typing_delay(self, message, substitute=True, translate=True): + if substitute: message = renpy.substitute(message, translate=translate) + return (len(message) / self.cps) + config.message_delay def __hash__(self): return hash(self.key) @@ -51,14 +36,14 @@ init -100 python in phone.character: if isinstance(x, Character): return x if x is None: x = store.pov_key if not has_character(x): - raise KeyError("nno phone Character with the key %r exists (check your definitions)" % x) + raise KeyError("no phone Character with the key %r exists (check your definitions)" % x) return _characters[x] def has_character(key): return key in _characters def get_textbox(color): - return RoundedFrame(color, radius=config.textbox_radius) + return RoundedCorners(color, radius=config.textbox_radius) def get_all(): return list(_characters.values()) diff --git a/game/00src/phone/phone/config.rpy b/game/00src/phone/phone/config.rpy index 7bbf2b5..b31ef82 100644 --- a/game/00src/phone/phone/config.rpy +++ b/game/00src/phone/phone/config.rpy @@ -5,8 +5,7 @@ init -100 python in phone.discussion: config.enter_transition = easeinbottom python early in phone.config: - from renpy import store - from store import Dissolve, _warper, _, phone, Transform + from renpy.store import store, config as renpy_config, Dissolve, _warper, _, phone, Transform _constant = True # Where the assets are located. @@ -55,8 +54,14 @@ python early in phone.config: # A dictionary mapping screen names to transforms or lists of transforms. # When a phone screen is shown, the screen name is looked up in the map (None is used if not found), # and the layer "master" is shown at those transforms. - layer_at_transforms = { } - # `None` is set later + layer_at_transforms = { + None: Transform(matrixcolor=renpy.display.matrix.Matrix([1, 0, 0, -0.03, # BrightnessMatrix(-0.03) + 0, 1, 0, -0.03, + 0, 0, 1, -0.03, + 0, 0, 0, 1]), + blur=2 + ) + } # How many "pages" of application we use in the `phone` screen. applications_pages = 4 @@ -70,6 +75,7 @@ python early in phone.config: "group_chats": list, "background_image": lambda: None, "calendars": list, + "group_chat_unread_pov": dict } def _generate_applications_dict(): @@ -108,6 +114,51 @@ python early in phone.config: # -For a video, `None`. discussion_callbacks = [ ] -init -1400 python in phone.config: - from store import BrightnessMatrix - layer_at_transforms[None] = None \ No newline at end of file + # The name of the layer usied in video calls. It is appended to `config.detached_layers` + video_call_layer = "phone_video_call" + + # A dict of transform properties applied to the `Layer` displayable used during a video call. + video_call_layer_transform_properties = { + "align": (0.5, 1.0), + "ysize": 1.0, + "fit": "contain" + } + + # How many messages we display at the same time. + messages_displayed = 175 + + # If the next "load" of messages contains this many or less messages, add those messages to the current load. + messages_fill_if_lower = 15 + + # A number of seconds added to the pause before each message. + message_delay = 0.6 + + # An index. The default calendar used when the `phone_calendars` screen is shown. + # If `True`, tries to retrieve the calendar that's in concordance to `phone.system.get_date()`. + default_calendar_index = -1 + + # If true, a group chat's "unreadness" is determined on the pov the group chat was read in. + # I.e, if the group chat was read in the "mc" pov, then it won't be marked as read in the "s" pov. + # If false, it is determined by whether the player has opened the group chat or not. + unread_group_chat_pov = False + + # If true, will define all images found in "assets/emojis" as emojis. + auto_emojis = True + + # The default value of the `delay` property for the `time` and `label` discussion statements. + default_label_delay = 0.5 + +python early: # prevent "default" + config.special_namespaces["store.phone.config"] = type(config.special_namespaces["store.config"])(phone.config, "phone.config") + +init 1500 python in phone: + @renpy_config.after_load_callbacks.append + def __ensure_phone_data_entries(): + changed = False + for d in data.values(): + for thing, f in config.data.items(): + if thing not in d: + d[thing] = f() + changed = True + if changed: + renpy.block_rollback() diff --git a/game/00src/phone/phone/definitions/01definitions.rpy b/game/00src/phone/phone/definitions/01definitions.rpy index 5fa6916..ed2092c 100644 --- a/game/00src/phone/phone/definitions/01definitions.rpy +++ b/game/00src/phone/phone/definitions/01definitions.rpy @@ -1,16 +1,15 @@ +default pc_damien = phone.character.Character("D-Man", phone.asset("damien_icon.png"), "D", 24, "#22Abf8") +default pc_olivia = phone.character.Character("Liv-Long", phone.asset("olivia_icon.png"), "O", 40, "#22Abf8") +default pc_sophia = phone.character.Character("Ms.P", phone.asset("sophia_icon.png"), "S", 20, "#22Abf8") +default pc_liz = phone.character.Character("Liz", phone.asset("liz_icon.png"), "L", 35, "#484848") +default pc_inco = phone.character.Character("Inco", phone.asset("inco_icon.png"), "I", 35, "#484848") + default pov_key = "I" -default pc_damien = phone.character.Character("D-Man", phone.config.basedir + "damien_icon.png", "D", 24, "#22Abf8") -default pc_olivia = phone.character.Character("Liv-Long", phone.config.basedir + "olivia_icon.png", "O", 40, "#22Abf8") -default pc_sophia = phone.character.Character("Ms.P", phone.config.basedir + "sophia_icon.png", "S", 20, "#22Abf8") -default pc_liz = phone.character.Character("Liz", phone.config.basedir + "liz_icon.png", "L", 35, "#484848") -default pc_inco = phone.character.Character("Inco", phone.config.basedir + "inco_icon.png", "I", 35, "#484848") - - -default damien_chat = phone.group_chat.GroupChat("D-Man", phone.config.basedir + "damien_icon.png", "damien_chat", ephemeral=True).add_character("D").add_character("I") -default livia_chat = phone.group_chat.GroupChat("Liv-Long", phone.config.basedir + "olivia_icon.png", "olivia_chat", ephemeral=True).add_character("O").add_character("I") -default sophia_chat = phone.group_chat.GroupChat("Ms.P", phone.config.basedir + "sophia_icon.png", "sophia_chat").add_character("S").add_character("I") # We don't care about if her chat gets cleared or not -default liz_chat = phone.group_chat.GroupChat("Liz", phone.config.basedir + "liz_icon.png", "liz_chat", ephemeral=True).add_character("L").add_character("I") +default damien_chat = phone.group_chat.GroupChat("D-Man", phone.asset("damien_icon.png"), "damien_chat", transient=True).add_character("D").add_character("I") +default livia_chat = phone.group_chat.GroupChat("Liv-Long", phone.asset("olivia_icon.png"), "olivia_chat", transient=True).add_character("O").add_character("I") +default sophia_chat = phone.group_chat.GroupChat("Ms.P", phone.asset("sophia_icon.png"), "sophia_chat").add_character("S").add_character("I") # We don't care about if her chat gets cleared or not +default liz_chat = phone.group_chat.GroupChat("Liz", phone.asset("liz_icon.png"), "liz_chat", transient=True).add_character("L").add_character("I") define EMOJI_PATH = "/gui/phone_assets/emojis/" @@ -61,3 +60,6 @@ init 100 python in phone.application: add_app_to_all_characters(message_app) add_app_to_all_characters(call_history_app) add_app_to_all_characters(calendar_app) + +init 100 python in phone.calendar: + add_calendar_to_all_characters(2023, 6, MONDAY) diff --git a/game/00src/phone/phone/emojis.rpy b/game/00src/phone/phone/emojis.rpy index 2b8223b..528c4ab 100644 --- a/game/00src/phone/phone/emojis.rpy +++ b/game/00src/phone/phone/emojis.rpy @@ -1,7 +1,7 @@ init -150 python in phone.emojis: - from renpy import store - from store import Transform + from renpy.store import store, Transform, phone from store.phone import config + _constant = True import string _NOT_ALLOWED_CHARACTERS = set(string.punctuation.strip("_") + " ") @@ -15,16 +15,13 @@ init -150 python in phone.emojis: global _emojis _emojis[name] = renpy.displayable(emoji) - - add("clueless", config.basedir + "emojis/clueless.png") - add("randomguy", config.basedir + "emojis/randomguy.png") - + def get(name): return _emojis[name] def _emoji_tag(tag, name): return [ - (renpy.TEXT_DISPLAYABLE, Transform(get(name), subpixel=True, ysize=1.0, fit="contain", yoffset=2)) + (renpy.TEXT_DISPLAYABLE, Transform(get(name), subpixel=True, ysize=1.0, fit="contain")) ] store.config.self_closing_custom_text_tags["emoji"] = _emoji_tag @@ -34,4 +31,32 @@ init -150 python in phone.emojis: def format_emoji_tag(s): for emoji in _tag_pattern.findall(s): s = _tag_pattern.sub(":" + emoji + ":", s, 1) - return s \ No newline at end of file + return s + +init 1000 python hide in phone.emojis: + if config.auto_emojis: + import os + + emoji_base_path = phone.asset("emojis") + + try: + for emoji in os.listdir(phone.path_join(store.config.basedir, "game", emoji_base_path)): + stuff = emoji.split(".") + + if len(stuff) != 2: continue + name, extension = stuff + + if extension.lower() not in ("png", "jpg", "jpeg", "svg"): + continue + + add(name, phone.path_join(emoji_base_path, emoji)) + + except OSError: + pass + +# prevent `default` +python early in phone.emojis: + pass + +python early: + config.special_namespaces["store.phone.emojis"] = type(config.special_namespaces["store.config"])(phone.emojis, "phone.emojis") diff --git a/game/00src/phone/phone/overlay/click_effects.rpy b/game/00src/phone/phone/overlay/click_effects.rpy index a12bda7..11c8336 100644 --- a/game/00src/phone/phone/overlay/click_effects.rpy +++ b/game/00src/phone/phone/overlay/click_effects.rpy @@ -8,7 +8,7 @@ screen _phone_click_effects(): define phone_click_uptime = 0.3 image phone_click_effect: - phone.config.basedir + "circle.png" + phone.asset("circle.png") alpha 0.34 matrixcolor TintMatrix("#464646") subpixel True xysize (25, 25) easein (phone_click_uptime * 0.4) xysize (50, 50) alpha 0.7 @@ -17,7 +17,7 @@ define phone_on_click_effect = ("phone_click_effect", phone_click_uptime) define phone_drag_uptime = 0.4 image phone_drag_effect: - phone.config.basedir + "circle.png" + phone.asset("circle.png") alpha 0.17 matrixcolor TintMatrix("#464646") subpixel True xysize (20, 20) easeout phone_drag_uptime alpha 0.0 xysize (0, 0) diff --git a/game/00src/phone/phone/overlay/status_bar.rpy b/game/00src/phone/phone/overlay/status_bar.rpy index 8182b49..8a4aa55 100644 --- a/game/00src/phone/phone/overlay/status_bar.rpy +++ b/game/00src/phone/phone/overlay/status_bar.rpy @@ -10,6 +10,7 @@ init -100 python in phone.status_bar: def battery_text_displayable(st, at): return Text("{}%".format(system.get_battery_level()), style="_phone_status_bar_text"), 0.1 + EMPTY = 0 ONE_FOURTH = 1 HALF = 2 THREE_FOURTH = 3 @@ -17,6 +18,7 @@ init -100 python in phone.status_bar: FULL = 5 _battery_displayables = { + EMPTY: renpy.displayable(config.basedir + "status_bar_battery_empty.png"), ONE_FOURTH: renpy.displayable(config.basedir + "status_bar_battery_one_fourth.png"), HALF: renpy.displayable(config.basedir + "status_bar_battery_half.png"), THREE_FOURTH: renpy.displayable(config.basedir + "status_bar_battery_three_fourth.png"), @@ -29,6 +31,9 @@ init -100 python in phone.status_bar: REDRAW = 0.5 + if battery_level == 0: + return _battery_displayables[EMPTY], REDRAW + if battery_level <= 33: # bamboozled return _battery_displayables[ONE_FOURTH], REDRAW @@ -90,12 +95,9 @@ screen _phone_status_bar(): add DynamicDisplayable(phone.status_bar.battery_text_displayable) add DynamicDisplayable(phone.status_bar.battery_displayable) at _fits(0.74) yalign 0.5 - action SetLocalVariable("control_center", True) + action (SetLocalVariable("control_center", True), SetField(phone.system, "at_list", _phone_control_center)) else: - on "show" action SetField(phone.system, "at_list", _phone_control_center) - on "hide" action SetField(phone.system, "at_list", _phone_status_bar) - add "#c4c4c438": at transform: on show: @@ -117,7 +119,7 @@ screen _phone_status_bar(): frame style "empty" modal True: button: style "empty" xfill True yfill True - action SetLocalVariable("control_center", False) + action (SetLocalVariable("control_center", False), SetField(phone.system, "at_list", _phone_status_bar)) hbox at Flatten: xalign 0.5 yanchor 1.0 ypos 0.98 @@ -125,7 +127,7 @@ screen _phone_status_bar(): vbox: use _phone_control_center_block(cols=2, rows=2, layout="grid"): button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "airplane_icon.png" + add phone.asset("airplane_icon.png") action If( phone.system.locked, NullAction(), @@ -133,7 +135,7 @@ screen _phone_status_bar(): ) button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "cellular_data_icon.png" + add phone.asset("cellular_data_icon.png") action If( phone.system.locked, NullAction(), @@ -142,11 +144,11 @@ screen _phone_status_bar(): ToggleVariable("phone.system.cellular_data") ) ) - selected_background Transform(phone.config.basedir + "circle.png", matrixcolor=TintMatrix("#35C759"), subpixel=True, fit="contain") + selected_background Transform(phone.asset("circle.png"), matrixcolor=TintMatrix("#35C759"), subpixel=True, fit="contain") padding (90, 90) button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "wifi_icon.png" + add phone.asset("wifi_icon.png") action ( If( phone.system.locked, @@ -165,7 +167,7 @@ screen _phone_status_bar(): ) button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "bluetooth_icon.png" + add phone.asset("bluetooth_icon.png") action If( phone.system.locked, NullAction(), @@ -179,21 +181,21 @@ screen _phone_status_bar(): use _phone_control_center_block(): button at _fits_block align (0.5, 0.5): if phone.system.rotation_locked: - add phone.config.basedir + "rotation_locked_icon.png" + add phone.asset("rotation_locked_icon.png") else: - add phone.config.basedir + "rotation_unlocked_icon.png" + add phone.asset("rotation_unlocked_icon.png") action ToggleVariable("phone.system.rotation_locked") use _phone_control_center_block(): button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "moon_icon.png" + add phone.asset("moon_icon.png") action ToggleVariable("phone.system.dark_mode") use _phone_control_center_block(cols=2): button at _yfits_block: style "empty" align (0.5, 0.5) hbox style "empty" yfill True: - add phone.config.basedir + "screen_mirroring_icon.png" at _yfits(0.67) yalign 0.5 + add phone.asset("screen_mirroring_icon.png") at _yfits(0.67) yalign 0.5 null width 10 text _("Screen\nMirroring") size 14 yalign 0.5 @@ -202,13 +204,13 @@ screen _phone_status_bar(): hbox: use _phone_control_center_block(): button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "flashlight_icon.png" + add phone.asset("flashlight_icon.png") action ToggleVariable("phone.system.flashlight") padding (30, 30) use _phone_control_center_block(): button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "timer_icon.png" + add phone.asset("timer_icon.png") action NullAction() padding (30, 30) hover_background None @@ -232,14 +234,14 @@ screen _phone_status_bar(): imagebutton: xysize (50, 50) - selected_idle Transform(phone.config.basedir + "play_icon.png", fit="contain", subpixel=True, align=(0.5, 0.5)) - selected_hover Transform(phone.config.basedir + "play_icon.png", fit="contain", subpixel=True, align=(0.5, 0.5)) + selected_idle Transform(phone.asset("play_icon.png"), fit="contain", subpixel=True, align=(0.5, 0.5)) + selected_hover Transform(phone.asset("play_icon.png"), fit="contain", subpixel=True, align=(0.5, 0.5)) if renpy.music.get_playing("phone_music") is None: - idle Transform(phone.config.basedir + "play_icon.png", fit="contain", subpixel=True, align=(0.5, 0.5)) + idle Transform(phone.asset("play_icon.png"), fit="contain", subpixel=True, align=(0.5, 0.5)) action (NullAction(), SelectedIf(False)) else: - idle Transform(phone.config.basedir + "pause_icon.png", fit="contain", subpixel=True, align=(0.5, 0.5)) + idle Transform(phone.asset("pause_icon.png"), fit="contain", subpixel=True, align=(0.5, 0.5)) action PauseAudio("phone_music", "toggle") button style "empty" yalign 0.5: @@ -252,7 +254,7 @@ screen _phone_status_bar(): hbox: use _phone_control_center_block(rows=2): vbar value FieldValue(phone.system, "brightness", 1.0 - phone.config.lowest_brightness, offset=phone.config.lowest_brightness) - add phone.config.basedir + "sun_icon.png": + add phone.asset("sun_icon.png"): at transform: subpixel True xysize (0.5, 0.5) fit "contain" xalign 0.5 yalign 0.75 @@ -264,11 +266,11 @@ screen _phone_status_bar(): add DynamicDisplayable( lambda st, at: ( ( - phone.config.basedir + "volume_icon_0.png" + phone.asset("volume_icon_0.png") if get_mixer("phone") == 0.0 - else phone.config.basedir + "volume_icon_1.png" + else phone.asset("volume_icon_1.png") if get_mixer("phone") < 0.5 - else phone.config.basedir + "volume_icon_2.png" + else phone.asset("volume_icon_2.png") ), 0.1 ) @@ -281,20 +283,20 @@ screen _phone_status_bar(): hbox: use _phone_control_center_block(): button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "calculator_icon.png" + add phone.asset("calculator_icon.png") action NullAction() padding (30, 30) hover_background None use _phone_control_center_block(): button at _fits_block align (0.5, 0.5): - add phone.config.basedir + "camera_icon.png" + add phone.asset("camera_icon.png") action NullAction() padding (30, 30) hover_background None screen _phone_control_center_block(cols=1, rows=1, layout="empty"): - frame at Flatten: - # https://github.com/renpy/renpy/issues/4666 >= - if renpy.version_tuple[:2] > ((7, 6) if renpy.compat.PY2 else (8, 1)): + frame at (CurriedRoundedCorners(radius=gui.phone_control_center_block_rounded_corners_radius), Flatten): + # https://github.com/renpy/renpy/issues/4666 + if is_renpy_version_or_above(7, 6, 1): xysize ( absolute((gui.phone_control_center_block_size * cols) + (gui.phone_control_center_spacing * (cols - 1))), absolute((gui.phone_control_center_block_size * rows) + (gui.phone_control_center_spacing * (rows - 1))) @@ -342,7 +344,7 @@ style _phone_status_bar_text is empty: color "#fff" outlines [ ] size 12 line_spacing 0 yalign 0.5 - font phone.config.basedir + "Aller_Rg.ttf" + font phone.asset("Aller_Rg.ttf") transform _fits_block(factor=gui.phone_control_center_block_scaling_factor): _fits(absolute(gui.phone_control_center_block_size * factor)) @@ -366,7 +368,7 @@ style _phone_control_center_hbox is empty: style _phone_control_center_vbox is empty: spacing gui.phone_control_center_spacing -image _phone_control_center_bar = RoundedFrame("#fafafa", radius=gui.phone_control_center_block_rounded_corners_radius, outline_color="#f7f7f7d8", outline_width=1) +image _phone_control_center_bar = "#fafafa" style _phone_control_center_bar is empty: left_bar "_phone_control_center_bar" @@ -378,13 +380,13 @@ style _phone_control_center_vbar is empty: style _phone_control_center_button is empty: padding (70, 70) background None - hover_background Transform(phone.config.basedir + "circle.png", matrixcolor=TintMatrix("#5a5a5a"), subpixel=True, fit="contain") - selected_background Transform(phone.config.basedir + "circle.png", matrixcolor=TintMatrix("#007BFA"), subpixel=True, fit="contain") - insensitive_background Transform(phone.config.basedir + "circle.png", matrixcolor=TintMatrix("#b1b1b1"), subpixel=True, fit="contain") + hover_background Transform(phone.asset("circle.png"), matrixcolor=TintMatrix("#5a5a5a"), subpixel=True, fit="contain") + selected_background Transform(phone.asset("circle.png"), matrixcolor=TintMatrix("#007BFA"), subpixel=True, fit="contain") + insensitive_background Transform(phone.asset("circle.png"), matrixcolor=TintMatrix("#b1b1b1"), subpixel=True, fit="contain") style _phone_control_center_block_frame is empty: modal True - background RoundedFrame("#000000d0", radius=gui.phone_control_center_block_rounded_corners_radius, outline_color="#00000093", outline_width=1) + background "#000000d0" style _phone_control_center_block_base_box is empty: spacing int(gui.phone_control_center_spacing + (gui.phone_control_center_block_size * (1.0 - gui.phone_control_center_block_scaling_factor) * 0.5)) diff --git a/game/00src/phone/phone_stuff.rpy b/game/00src/phone/phone_stuff.rpy index a51f7a9..b7f4fe9 100644 --- a/game/00src/phone/phone_stuff.rpy +++ b/game/00src/phone/phone_stuff.rpy @@ -2,6 +2,7 @@ init -999: define config.gl2 = True define config.early_start_store = False +# Some of these definitions are overwrote in 01definitions.rpy init -200: define gui.phone_margin = (15, 81, 15, 94) define gui.phone_zoom = 0.8 @@ -30,6 +31,22 @@ init -200: python early: import collections, pygame_sdl2 as pygame + @renpy.pure + def is_renpy_version_or_above(major, minor, patch): + """ + Checks if the current version of renpy is at least `(major, minor, patch)`. + If renpy is on a py3 / r8 version, `(major + 1, minor - 5, patch)` is checked. + + I.e., if the game runs on renpy 8 and that this function is called to check for `7.5.0`, `8.0.0` (the py3 / r8 equivalent of `7.5.0`) is checked for. + """ + current_version = renpy.version_tuple[:3] + + if not renpy.compat.PY2: + major += 1 + minor -= 5 + + return current_version >= (major, minor, patch) + def hyperlink_functions_style(name): """ Hyperlink functions but the style `name` is used. @@ -40,7 +57,7 @@ python early: style_object = getattr(style, name) return (lambda target: style_object,) + style.default.hyperlink_functions[1:] - if renpy.version_tuple[:2] >= ((7, 6) if renpy.compat.PY2 else (8, 1)): + if is_renpy_version_or_above(7, 6, 0): def get_mixer(mixer): return preferences.get_mixer(mixer) else: @@ -65,67 +82,72 @@ python early: b = a * col[2] / 255.0 return (r, g, b, a) - # RoundedFrame by pseurae - # https://gist.github.com/Pseurae/661e6084f756fc917b2889a386b16664 - # modified by yours truly (i don't know shit about OpenGL) - class RoundedFrame(renpy.display.image.Frame): - def __init__(self, image, *args, **kwargs): - radius = kwargs.pop("radius", 0.0) - outline_width = kwargs.pop("outline_width", 0.0) - outline_color = kwargs.pop("outline_color", "#fff") + _rounded_corners_relative = { + None: 0.0, + "min": 1.0, + "max": 2.0, + "width": 3.0, + "height": 4.0, + } - super(RoundedFrame, self).__init__(image, *args, **kwargs) - - if not isinstance(radius, tuple): radius = (radius,) * 4 - self.radius = radius + def RoundedCorners(child, radius, relative=None, outline_width=0.0, outline_color="#fff", **kwargs): + if not isinstance(radius, tuple): radius = (radius,) * 4 + relative = _rounded_corners_relative[relative] + outline_color = normalize_color(Color(outline_color)) + return Transform(child, mesh=True, shader="shader.rounded_corners", u_radius=radius, u_relative=relative, u_outline_color=outline_color, u_outline_width=outline_width, **kwargs) - self.outline_width = outline_width - self.outline_color = normalize_color(Color(outline_color)) - - def render(self, w, h, st, at): - rv = super(RoundedFrame, self).render(w, h, st, at) - - if self.radius: - rv.mesh = True - rv.add_property("gl_pixel_perfect", True) - rv.add_property("gl_mipmap", False) - rv.add_property("texture_scaling", "nearest") - rv.add_shader("shader.rounded_corners") - rv.add_uniform("u_radius", self.radius) - rv.add_uniform("u_outline_width", self.outline_width) - rv.add_uniform("u_outline_color", self.outline_color) - - return rv + CurriedRoundedCorners = renpy.curry(RoundedCorners) renpy.register_shader("shader.rounded_corners", variables=""" uniform vec4 u_radius; uniform float u_outline_width; uniform vec4 u_outline_color; + uniform float u_relative; uniform sampler2D tex0; attribute vec2 a_tex_coord; varying vec2 v_tex_coord; - uniform vec2 res0; uniform vec2 u_model_size; """, vertex_200=""" v_tex_coord = a_tex_coord; """, fragment_functions=""" - // https://www.iquilezles.org/www/articles/distfunctions/distfunctions2d.htm - float rounded_rectangle(in vec2 p, in vec2 b, in vec4 r) { - r.xy = (p.x > 0.0) ? r.xy : r.zw; - r.x = (p.y > 0.0) ? r.x : r.y; - vec2 q = abs(p) - b + r.x; - return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; + float rounded_rectangle(vec2 p, vec2 b, float r) { + return length(max(abs(p) - b + r, 0.0)) - r; + } + + float get_radius(vec2 uv_minus_center, vec4 radius) { + vec2 xy = (uv_minus_center.x > 0.0) ? radius.xy : radius.zw; + float r = (uv_minus_center.y > 0.0) ? xy.x : xy.y; + return r; } """, fragment_200=""" vec2 center = u_model_size.xy / 2.0; vec2 uv = (v_tex_coord.xy * u_model_size.xy); + + vec2 uv_minus_center = uv - center; + float radius = get_radius(uv_minus_center, u_radius); + vec4 color = texture2D(tex0, v_tex_coord); + if (u_relative != 0.0) { + float side_size; + if (u_relative == 1.0) { + side_size = u_model_size.x; + } else if (u_relative == 2.0) { + side_size = u_model_size.y; + } else if (u_relative == 3.0) { + side_size = min(u_model_size.x, u_model_size.y); + } else { + side_size = max(u_model_size.x, u_model_size.y); + } + + radius *= side_size; + } + if (u_outline_width > 0.0) { vec2 center_outline = center - u_outline_width; - float crop1 = rounded_rectangle(uv - center, center, vec4(u_radius)); - float crop2 = rounded_rectangle(uv - center, center_outline, vec4(u_radius - u_outline_width)); + float crop1 = rounded_rectangle(uv - center, center, radius); + float crop2 = rounded_rectangle(uv - center, center_outline, radius - u_outline_width); float coeff1 = smoothstep(1.0, -1.0, crop1); float coeff2 = smoothstep(1.0, -1.0, crop2); @@ -135,11 +157,12 @@ python early: gl_FragColor = mix(vec4(0.0), mix(color, u_outline_color, outline_coeff), coeff1); } else { - float crop = rounded_rectangle(uv - center, center, vec4(u_radius)); - gl_FragColor = mix(vec4(0.0), color, smoothstep(1.0, 0.0, crop)); + float crop = rounded_rectangle(uv_minus_center, center, radius); + gl_FragColor = mix(color, vec4(0.0), smoothstep(0.0, 1.0, crop)); } """) + # taken from # https://github.com/WretchedTeam/WintermuteV3/blob/main/game/mod_code/backend/displayables.rpy class ShaderDisplayable(renpy.Displayable): diff --git a/game/00src/renpy-discord-presence/settings.rpy b/game/00src/renpy-discord-presence/settings.rpy index 3900807..9958159 100644 --- a/game/00src/renpy-discord-presence/settings.rpy +++ b/game/00src/renpy-discord-presence/settings.rpy @@ -50,5 +50,5 @@ define discord.start_label = "storyline" # These variables control the print functions across this script. They work if True, they're ignored if False. # Prints are shown inside game's console (if turned on in the Ren'Py Launcher) and in the game's log.txt file. define discord.log_important = True # Shows whether the Presence was initialized and closed correctly. -define discord.log_properties = True # Records properties whenever they change with set or update methods. +define discord.log_properties = False # Records properties whenever they change with set or update methods. define discord.log_restore = True # Notes whenever the properties get rolled back or loaded from a save file, and what they were restored into. \ No newline at end of file diff --git a/game/00src/save_compat.rpy b/game/00src/save_compat.rpy index 17835d7..b1fc520 100644 --- a/game/00src/save_compat.rpy +++ b/game/00src/save_compat.rpy @@ -9,13 +9,27 @@ default ben_story = None label after_load: if not renpy.android: + + # renpy.get_return_stack()[1] will basically return what chapter the user is currently in the save, we call index 1 since index 0 will always be the label storyline from storyline.rpy. Keep in mind the name used will be what the 'from clause' adds in the call + # If the player went past chapter 2 without seeing the caligraphy CG then they are on a save from the first version of the game - if not persistent.seen_notice and not renpy.seen_image("cg_calligraphy") and (renpy.get_return_stack()[1][0] == '_call_chapter_3' or renpy.get_return_stack()[1][0] == '_call_chapter_4'): + if not persistent.seen_notice and not renpy.seen_image("cg_calligraphy") and (renpy.get_return_stack()[1] == '_call_chapter_3' or renpy.get_return_stack()[1] == '_call_chapter_4'): call old_save_notice from _call_old_save_notice call ask_drawing from _call_ask_drawing $ renpy.block_rollback() - if ben_story == None and isinstance(renpy.get_return_stack()[1][0], str) and renpy.get_return_stack()[1][0].startswith("chapter_"): + # However this is not always reliable, sometimes it will fail, this is why it's in a try block and why we check that + # 1. We are checking a story script ("_call_chapter_"), since all of this won't work in mods and such + # 2. Check we are actually checking a string, since sometimes index 1 will be a tuple that points to a specific line instead (I don't know why this happens) + + python: + try: + needs_compat = isinstance(renpy.get_return_stack()[1], str) and renpy.get_return_stack()[1].startswith("_call_chapter_") + except: + needs_compat = False + + if ben_story == None and needs_compat: + # This ensures that mods won't trigger the save compatibility if hasattr(store, 'score_olivia'): call initstats(score_olivia,score_inco) from _call_initstats_4 call ask_drawing from _call_ask_drawing_1 @@ -123,6 +137,8 @@ label AskChoices: jump newContent + +# Adds a custom error handler for the error of 'Couldn't find a place to stop rolling back' (Which usually happens when the save is incompatible). This error handler sends the user to a screen informing them of this and then to the menu instead of showing the gray exception screen. init python: def handle_bad_save(shortException,longException,tracebackFile): if "Exception: Couldn't find a place to stop rolling back. Perhaps the script changed in an incompatible way?" in shortException and not config.developer: @@ -155,13 +171,16 @@ screen badSave(): xalign 0.5 spacing 100 - # I originally wanted to send you to the main menu but that really really breaks for some reason textbutton _("OK") activate_sound "audio/ui/snd_ui_click.wav" action Jump("kill_playthrough") + +# For some reason, sending to the main menu won't work and instead it'll start a new (broken) game. SO instead, we clear the return stack and then return. Since it's cleared Renpy thinks "The game's over, send them back to the menu" and this for some reason jest werks. label kill_playthrough: # Stupid way to return to the main menu, the traditional ways don't work or outright crash the game $ renpy.set_return_stack([]) return + +# If there's no drawing saved we ask the player to either draw a new one or to import it label ask_drawing: if type(waniDemoCarryover.Chapter3Drawing) == NoneType: @@ -195,6 +214,7 @@ label ask_drawing: with open(filename, mode="rb") as filecontents: waniDemoCarryover.Chapter3Drawing = filecontents.read() + # We close the file just in case and then we replace with None otherwise renpy tries to pickle the variable for saving and fails. filecontents.close() filecontents = None @@ -211,7 +231,7 @@ label ask_drawing: shutil.copyfile(filename, new_folder) except: pass - # Needed unless renpy throws a fit when saving + # Same as with filecontents. Needed unless renpy throws a fit when saving. renpy.store.shutil = None if filename == None or not filename.endswith(".png"): diff --git a/game/00src/splashscreen.rpy b/game/00src/splashscreen.rpy index 810a2ba..72b450b 100644 --- a/game/00src/splashscreen.rpy +++ b/game/00src/splashscreen.rpy @@ -1,16 +1,9 @@ label splashscreen: - $ persistent.splashtype = random.randint(0,2000 - 1) - - show anim_caveintro - play sound mus_startup - pause 11.2 - stop sound - - if (persistent.languaged_up is None): - $ persistent.languaged_up = True - $ preferences.set_volume('ui', config.default_sfx_volume) # hack - call screen lang_sel - + if not renpy.get_autoreload(): + show anim_caveintro + play sound mus_startup + pause 11.2 + stop sound return \ No newline at end of file diff --git a/game/00src/translation.rpy b/game/00src/translation.rpy index a7bc9e4..b6fc4f4 100644 --- a/game/00src/translation.rpy +++ b/game/00src/translation.rpy @@ -25,7 +25,7 @@ screen OkPrompt(message, go_menu): xalign 0.5 spacing 100 - textbutton _("OK") activate_sound "audio/ui/snd_ui_click.wav" action If(go_menu, true=MainMenu(False,False), false=Return()) + textbutton _("OK") activate_sound "audio/ui/snd_ui_click.wav" action If(go_menu, true=MainMenu(False,False), false=[Hide(),Return()]) screen hiddenOkPrompt(message, go_menu): @@ -133,9 +133,9 @@ screen lang_sel(): idle darkie(languages[i]["image"]) hover glowie(languages[i]["image"]) action If(languages[i]["value"] in persistent.seenWarning or languages[i]["value"] == None, - true = [Language(languages[i]["value"]), MainMenu(False,False)], + true = [Language(languages[i]["value"]), Return()], # Important to change the language before calling notice. Otherwise it will be in english. - false = [Language(languages[i]["value"]), AddToSet(set=persistent.seenWarning, value=languages[i]["value"]), Show(screen="OkPrompt", message=notice, go_menu=True)] + false = [Language(languages[i]["value"]), AddToSet(set=persistent.seenWarning, value=languages[i]["value"]), Show(screen="OkPrompt", message=notice, go_menu=False)] ) at renpysdumb # Scales the imagebutton down. No, you can't just specify the zoom here. It has to be a defined transform. else: diff --git a/game/dev/actioneditor/ATL_functions.rpy b/game/dev/actioneditor/ATL_functions.rpy index 6b8c94b..efe1f18 100644 --- a/game/dev/actioneditor/ATL_functions.rpy +++ b/game/dev/actioneditor/ATL_functions.rpy @@ -194,7 +194,13 @@ init python: damp = 1 if self.damped and self.end is not None: damp = renpy.atl.warpers[self.damped_warper]((self.end - st / self.fast_forward) / self.end) - offset = damp * renpy.atl.interpolate_spline(g, self.random_knot) + + if renpy.version_tuple[3] >= 24011600: + interpolate_spline = renpy.atl.interpolate_spline(g, self.random_knot, renpy.atl.PROPERTIES[self.property]) + else: + interpolate_spline = renpy.atl.interpolate_spline(g, self.random_knot) + + offset = damp * interpolate_spline setattr(tran, self.property, offset) return 0 diff --git a/game/dev/actioneditor/ActionEditor.rpy b/game/dev/actioneditor/ActionEditor.rpy index a9d499c..50154fb 100644 --- a/game/dev/actioneditor/ActionEditor.rpy +++ b/game/dev/actioneditor/ActionEditor.rpy @@ -39,13 +39,29 @@ init python in _viewers: from renpy.store import InvertMatrix, ContrastMatrix, SaturationMatrix, BrightnessMatrix, HueMatrix def action_editor_version(): - return "230825_1" + return "231223_1" + def check_new_position_type(v): + if not check_version(24010100): + return False + elif isinstance(v, renpy.atl.position): + return True + else: + return False if check_version(23032500): euler_slerp = renpy.display.quaternion.euler_slerp - else: + elif check_version(23030700): euler_slerp = renpy.display.accelerator.quaternion + else: + euler_slerp = None + + def get_layers(): + r = [] + for l in config.layers: + if l not in not_included_layer: + r.append(l) + return r #z -> y -> x order roate def rotate_matrix2(_, x, y, z): @@ -171,7 +187,7 @@ init -1598 python in _viewers: def action_editor_init(): - global image_state, image_state_org, camera_state_org, movie_cache + global image_state, image_state_org, camera_state_org, movie_cache, third_view_child sle = renpy.game.context().scene_lists # layer->tag->property->value @@ -182,54 +198,69 @@ init -1598 python in _viewers: image_state.append({}) camera_state_org.append({}) movie_cache = {} - d = sle.camera_transform["master"] + third_view_child = {} - # child = getattr(d, "child", None) - # child = getattr(child, "child", None) - # if child is not None: - # renpy.store._viewers.at_clauses_flag = True - child = getattr(d, "child", None) - at_list = [] - while child is not None and type(child) is not renpy.display.layout.MultiBox: - trans = get_transform_name(child) - if trans is not None: - at_list.append(trans) - child = getattr(child, "child", None) - at_list.reverse() - camera_state_org[current_scene]["at_list"] = at_list + for layer in get_layers(): + d = sle.camera_transform[layer] - #cameraはget_placementを使用すると座標が所得できない - # if d is not None: - # pos = renpy.get_placement(d) - pos = d - state = getattr(d, "state", None) - for p in {"xpos", "ypos", "xanchor", "yanchor", "xoffset", "yoffset"}: - camera_state_org[current_scene][p] = getattr(pos, p, None) - for p in camera_props: - if p not in camera_state_org[current_scene]: - if p in ("matrixtransform", "matrixcolor"): - for prop, v in load_matrix(p, getattr(state, p, None)): + # child = getattr(d, "child", None) + # child = getattr(child, "child", None) + # if child is not None: + # renpy.store._viewers.at_clauses_flag = True + + child = getattr(d, "child", None) + at_list = [] + while child is not None and type(child) is not renpy.display.layout.MultiBox: + trans = get_transform_name(child) + if trans is not None: + at_list.append(trans) + child = getattr(child, "child", None) + at_list.reverse() + camera_state_org[current_scene][layer] = {} + camera_state_org[current_scene][layer]["at_list"] = at_list + + #cameraはget_placementを使用すると座標が所得できない + # if d is not None: + # pos = renpy.get_placement(d) + pos = d + state = getattr(d, "state", None) + for p in {"xpos", "ypos", "xanchor", "yanchor"}: + v = getattr(pos, p, None) + if check_new_position_type(v): + if v.absolute == 0: + v = float(v.relative) + elif v.relative == 0: + v = int(v.absolute) + camera_state_org[current_scene][layer][p] = v + else: + camera_state_org[current_scene][layer][p] = v + for p in {"xoffset", "yoffset"}: + camera_state_org[current_scene][layer][p] = getattr(pos, p, None) + for p in camera_props: + if p not in camera_state_org[current_scene][layer]: + if p in ("matrixtransform", "matrixcolor"): + for prop, v in load_matrix(p, getattr(state, p, None)): + if is_force_float(p) and isinstance(v, int): + v = float(v) + camera_state_org[current_scene][layer][prop] = v + else: + v = getattr(state, p, None) if is_force_float(p) and isinstance(v, int): v = float(v) - camera_state_org[current_scene][prop] = v - else: - v = getattr(state, p, None) - if is_force_float(p) and isinstance(v, int): - v = float(v) - camera_state_org[current_scene][p] = v - for gn, ps in props_groups.items(): - for p in camera_props: - if p in ps: - pvs = getattr(state, gn, None) - if pvs is not None: - for gp, v in zip(ps, pvs): - if is_force_float(gp) and isinstance(v, int): - v = float(v) - camera_state_org[current_scene][gp] = v - break + camera_state_org[current_scene][layer][p] = v + for gn, ps in props_groups.items(): + for p in camera_props: + if p in ps: + pvs = getattr(state, gn, None) + if pvs is not None: + for gp, v in zip(ps, pvs): + if is_force_float(gp) and isinstance(v, int): + v = float(v) + camera_state_org[current_scene][layer][gp] = v + break - for layer in config.layers: + for layer in get_layers(): image_state_org[current_scene][layer] = {} image_state[current_scene][layer] = {} for image in sle.layers[layer]: @@ -277,6 +308,16 @@ init -1598 python in _viewers: pos = renpy.get_placement(d) state = getattr(d, "state", None) for p in {"xpos", "ypos", "xanchor", "yanchor", "xoffset", "yoffset"}: + v = getattr(pos, p, None) + if check_new_position_type(v): + if v.absolute == 0: + v = float(v.relative) + elif v.relative == 0: + v = int(v.absolute) + image_state_org[current_scene][layer][tag][p] = v + else: + image_state_org[current_scene][layer][tag][p] = v + for p in {"xoffset", "yoffset"}: image_state_org[current_scene][layer][tag][p] = getattr(pos, p, None) for p in transform_props: if p not in image_state_org[current_scene][layer][tag]: @@ -304,7 +345,7 @@ init -1598 python in _viewers: break # init camera, layer and images - for layer in config.layers: + for layer in get_layers(): renpy.scene(layer) sle.set_layer_at_list(layer, [], camera=True) sle.set_layer_at_list(layer, []) @@ -373,14 +414,15 @@ init -1598 python in _viewers: # return rv - def check_props_group(prop, tag=None, scene_num=None): + def check_props_group(key, scene_num=None): + tag, layer, prop = key + #tag = (None, "layer") express camera if prop.count("_") == 3 and prop.split("_")[0] in ("matrixtransform", "matrixcolor"): if scene_num is None: scene_num = current_scene - if tag == "camera": - state = camera_state_org[scene_num] + if tag is None: + state = camera_state_org[scene_num][layer] else: - tag, layer = tag state = get_image_state(layer, scene_num)[tag] gn = prop.split("_")[0] @@ -488,10 +530,7 @@ init -1598 python in _viewers: def is_wide_range(key, scene_num=None): if scene_num is None: scene_num = current_scene - if isinstance(key, tuple): - tag, layer, prop = key - else: - prop = key + tag, layer, prop = key if prop.count("_") == 3: sign, _, _, prop2 = prop.split("_") if sign in ("matrixtransform", "matrixcolor"): @@ -506,15 +545,18 @@ init -1598 python in _viewers: if key in all_keyframes[current_scene]: v = all_keyframes[current_scene][key][-1][0] else: - if isinstance(key, tuple): - state = get_image_state(layer)[tag] + if tag is None: + state = camera_state_org[current_scene][layer] else: - state = camera_state_org[current_scene] + state = get_image_state(layer)[tag] if state[prop] is not None: v = state[prop] else: v = get_default(prop) - return isinstance(v, int) + if isinstance(v, int) or check_new_position_type(v): + return True + else: + return False def reset(key_list, time=None): @@ -526,12 +568,11 @@ init -1598 python in _viewers: if not isinstance(key_list, list): key_list = [key_list] for key in key_list: - if isinstance(key, tuple): - tag, layer, prop = key - state = get_image_state(layer)[tag] + tag, layer, prop = key + if tag is None: + state = camera_state_org[current_scene][layer] else: - prop = key - state = camera_state_org[current_scene] + state = get_image_state(layer)[tag] v = state[prop] if v is None: v = get_default(prop) @@ -541,21 +582,21 @@ init -1598 python in _viewers: def image_reset(): - key_list = [(tag, layer, prop) for layer in config.layers for tag, props in get_image_state(layer).items() for prop in props] + key_list = [(tag, layer, prop) for layer in get_layers() for tag, props in get_image_state(layer).items() for prop in props] reset(key_list) def camera_reset(): - reset([p for p in camera_state_org[current_scene]]) + key_list = [(None, layer, prop) for layer in get_layers() for prop in camera_state_org[current_scene][layer].keys()] + reset(key_list) def generate_changed(key): - if isinstance(key, tuple): - tag, layer, prop = key - state = get_image_state(layer)[tag] + tag, layer, prop = key + if tag is None: + state = camera_state_org[current_scene][layer] else: - prop = key - state = camera_state_org[current_scene] + state = get_image_state(layer)[tag] def changed(v, time=None, knot_number=None): if time is None: time = current_time @@ -571,12 +612,17 @@ init -1598 python in _viewers: elif v < 0: v = 0 v = round(float(v), 2) - else: + elif isinstance(get_value(key, default=True), int): if not is_force_plus(prop): v -= persistent._wide_range elif v < 0: v = 0 v = int(v) + elif check_new_position_type(get_value(key, default=True)): + if not is_force_plus(prop): + v = renpy.atl.position.from_any(v) - renpy.atl.position.from_any(persistent._wide_range) + # elif v < 0: + # v = 0 else: if isinstance(get_value(key, default=True), float): if not is_force_plus(prop): @@ -584,12 +630,17 @@ init -1598 python in _viewers: elif v < 0: v = 0 v = round(float(v), 2) - else: + elif isinstance(get_value(key, default=True), int): if not is_force_plus(prop): v -= persistent._narrow_range elif v < 0: v = 0 v = int(v) + elif check_new_position_type(get_value(key, default=True)): + if not is_force_plus(prop): + v = renpy.atl.position.from_any(v) - renpy.atl.position.from_any(persistent._narrow_range) + # elif v < 0: + # v = 0 if knot_number is None: default_warper_org = persistent._viewer_warper @@ -614,18 +665,18 @@ init -1598 python in _viewers: if force_plus: return value else: - return value + range + if check_new_position_type(value): + return value + renpy.atl.position(range) + else: + return value + range def set_keyframe(key, value, recursion=False, time=None): - if isinstance(key, tuple): - tag, layer, prop = key - mkey = (tag, layer) - state = get_image_state(layer)[tag] + tag, layer, prop = key + if tag is None: + state = camera_state_org[current_scene][layer] else: - prop = key - mkey = "camera" - state = camera_state_org[current_scene] + state = get_image_state(layer)[tag] if time is None: time = current_time keyframes = all_keyframes[current_scene].get(key, []) @@ -654,30 +705,28 @@ init -1598 python in _viewers: (value, time, persistent._viewer_warper)] if not recursion: - check_result = check_props_group(prop, mkey) + check_result = check_props_group(key) if check_result is not None: gn, ps = check_result if gn != "focusing": ps_set = set(ps) ps_set.remove(prop) for p in ps_set: - if isinstance(key, tuple): - key2 = (tag, layer, p) - else: - key2 = p + key2 = (tag, layer, p) set_keyframe(key2, get_value(key2, default=True), True, time=time) if not recursion: for s in range(current_scene+1, len(scene_keyframes)): - for i in range(s, -1, -1): - if camera_keyframes_exist(i): - break - for p in camera_state_org[i]: - if p in camera_state_org[s]: - middle_value = get_value(p, scene_keyframes[s][1], False, i) - if isinstance(middle_value, float): - camera_state_org[s][p] = round(middle_value, 2) - else: - camera_state_org[s][p] = middle_value + for layer in get_layers(): + for i in range(s, -1, -1): + if camera_keyframes_exist(i, layer): + break + for p in camera_state_org[i][layer]: + if p in camera_state_org[s][layer]: + middle_value = get_value((None, layer, p), scene_keyframes[s][1], False, i) + if isinstance(middle_value, float): + camera_state_org[s][layer][p] = round(middle_value, 2) + else: + camera_state_org[s][layer][p] = middle_value def generate_matrix_strings(args, matrix, ps, side_view=False): @@ -735,146 +784,147 @@ init -1598 python in _viewers: files = renpy.python.py_eval(times[current_time], locals=renpy.python.store_dicts["store.audio"]) renpy.music.play(files, channel, loop=False) - camera_check_points = [] - viewer_check_points = [] - loop = [] - spline = [] - for s, (_, t, _) in enumerate(scene_keyframes): - check_points = {} - vcheck_points = {} - camera_is_used = False - props_use_default = [] - at_list = camera_state_org[0].get("at_list") - check_points["at_list"] = [(at_list, 0, None)] + for layer in get_layers(): + camera_check_points = [] + viewer_check_points = [] + loop = [] + spline = [] + for s, (_, t, _) in enumerate(scene_keyframes): + check_points = {} + vcheck_points = {} + camera_is_used = False + props_use_default = [] + check_points = {} + vcheck_points = {} + at_list = camera_state_org[0][layer].get("at_list") + check_points["at_list"] = [(at_list, 0, None)] - for prop in camera_state_org[s]: - if not exclusive_check(prop, s): - continue - if prop in all_keyframes[s]: - check_points[prop] = all_keyframes[s][prop] - camera_is_used = True - else: - if camera_state_org[s][prop] is not None: - check_points[prop] = [(get_value(prop, default=False, scene_num=s), t, None)] - elif prop not in not_used_by_default: - check_points[prop] = [(get_value(prop, default=True, scene_num=s), t, None)] - props_use_default.append(prop) + for prop in camera_state_org[s][layer]: + if not exclusive_check((None, layer, prop), s): + continue + if (None, layer, prop) in all_keyframes[s]: + check_points[prop] = all_keyframes[s][(None, layer, prop)] + camera_is_used = True + else: + if camera_state_org[s][layer][prop] is not None: + check_points[prop] = [(get_value((None, layer, prop), default=False, scene_num=s), t, None)] + elif prop not in not_used_by_default: + check_points[prop] = [(get_value((None, layer, prop), default=True, scene_num=s), t, None)] + props_use_default.append(prop) - check_points["props_use_default"] = [(props_use_default, t, None)] - #focusing以外のグループプロパティーはここで纏める - included_gp = {} - for p in check_points: - check_result = check_props_group(p, "camera", s) - if check_result is not None: - gn, ps = check_result - if gn != "focusing" and gn not in included_gp: - args = [] - for prop in ps: - args.append(check_points[prop]) - group_cs = [] - for cs in zip(*args): - v = tuple(c[0] for c in cs) - if gn in ("matrixtransform", "matrixcolor"): - v = generate_matrix_strings(v, gn, ps) - v = renpy.python.py_eval(v) - group_cs.append((v, cs[0][1], cs[0][2])) - included_gp[gn] = (ps, group_cs) - - #viewer上ではmatrixtransformは符号と順番が反転する。回転順も反転するためz-y-x順の回転行列がないと対応不可 - if gn == "matrixtransform" and persistent._viewer_sideview: - viewer_group_cs = [] - #ScaleMatrixのみ逆数をとる - scale_ps = [] - for i, prop in enumerate(ps): - _, _, _, p_name = prop.split("_") - if p_name.startswith("scale"): - scale_ps.append(i) + check_points["props_use_default"] = [(props_use_default, t, None)] + #focusing以外のグループプロパティーはここで纏める + included_gp = {} + for p in check_points: + check_result = check_props_group((None, layer, p), s) + if check_result is not None: + gn, ps = check_result + if gn != "focusing" and gn not in included_gp: + args = [] + for prop in ps: + args.append(check_points[prop]) + group_cs = [] for cs in zip(*args): - org_v = tuple(c[0] for c in cs) - viewer_v = [] - for i, e in enumerate(org_v): - if i in scale_ps: - if e == 0: - viewer_v.append(1000.) #inf + v = tuple(c[0] for c in cs) + if gn in ("matrixtransform", "matrixcolor"): + v = generate_matrix_strings(v, gn, ps) + v = renpy.python.py_eval(v) + group_cs.append((v, cs[0][1], cs[0][2])) + included_gp[gn] = (ps, group_cs) + + #viewer上ではmatrixtransformは符号と順番が反転する。回転順も反転するためz-y-x順の回転行列がないと対応不可 + if gn == "matrixtransform" and persistent._viewer_sideview: + viewer_group_cs = [] + #ScaleMatrixのみ逆数をとる + scale_ps = [] + for i, prop in enumerate(ps): + _, _, _, p_name = prop.split("_") + if p_name.startswith("scale"): + scale_ps.append(i) + for cs in zip(*args): + org_v = tuple(c[0] for c in cs) + viewer_v = [] + for i, e in enumerate(org_v): + if i in scale_ps: + if e == 0: + viewer_v.append(1000.) #inf + else: + viewer_v.append(1.0/e) else: - viewer_v.append(1.0/e) - else: - viewer_v.append(-e) - #matrixの順番を反転 marixの引数が3個単位と前提 - viewer_ps = [] - rviewer_v = [] - start_index = range(0, len(ps), 3) - start_index.reverse() - for i in start_index: - viewer_ps.extend((ps[i], ps[i+1], ps[i+2])) - rviewer_v.extend((viewer_v[i], viewer_v[i+1], viewer_v[i+2])) + viewer_v.append(-e) + #matrixの順番を反転 marixの引数が3個単位と前提 + viewer_ps = [] + rviewer_v = [] + start_index = range(0, len(ps), 3) + start_index.reverse() + for i in start_index: + viewer_ps.extend((ps[i], ps[i+1], ps[i+2])) + rviewer_v.extend((viewer_v[i], viewer_v[i+1], viewer_v[i+2])) - viewer_v = generate_matrix_strings(tuple(rviewer_v), gn, viewer_ps, True) - viewer_v = renpy.python.py_eval(viewer_v) - viewer_group_cs.append((viewer_v, cs[0][1], cs[0][2])) - vcheck_points[gn] = viewer_group_cs + viewer_v = generate_matrix_strings(tuple(rviewer_v), gn, viewer_ps, True) + viewer_v = renpy.python.py_eval(viewer_v) + viewer_group_cs.append((viewer_v, cs[0][1], cs[0][2])) + vcheck_points[gn] = viewer_group_cs - for gn, (ps, group_cs) in included_gp.items(): - for prop in ps: - del check_points[prop] - check_points[gn] = group_cs + for gn, (ps, group_cs) in included_gp.items(): + for prop in ps: + del check_points[prop] + check_points[gn] = group_cs - #viewerで使用するプロパティー(functionはblur等が含まれる可能性がある) - #These properties are shown in side viewer(function property has danger of including blur) - if persistent._viewer_sideview: - for p in ("xpos", "xanchor", "xoffset", "ypos", "yanchor", "yoffset", "zpos", "rotate", "xrotate", "yrotate", "zrotate", "orientation", "point_to"): - if p in check_points: - vcheck_points[p] = check_points[p] - vcheck_points["props_use_default"] = check_points["props_use_default"] - vcheck_points["at_list"] = check_points["at_list"] + #viewerで使用するプロパティー(functionはblur等が含まれる可能性がある) + #These properties are shown in side viewer(function property has danger of including blur) + if persistent._viewer_sideview: + for p in ("xpos", "xanchor", "xoffset", "ypos", "yanchor", "yoffset", "zpos", "rotate", "xrotate", "yrotate", "zrotate", "orientation", "point_to"): + if p in check_points: + vcheck_points[p] = check_points[p] + vcheck_points["props_use_default"] = check_points["props_use_default"] + vcheck_points["at_list"] = check_points["at_list"] - if not camera_is_used and s > 0: - loop.append(loop[s-1]) - spline.append(spline[s-1]) - camera_check_points.append(camera_check_points[s-1]) - viewer_check_points.append(viewer_check_points[s-1]) - else: - loop.append({key+"_loop": loops[s][key] for key in loops[s] if not isinstance(key, tuple)}) - spline.append({key+"_spline": splines[s][key] for key in splines[s] if not isinstance(key, tuple)}) - camera_check_points.append(check_points) - viewer_check_points.append(vcheck_points) + if not camera_is_used and s > 0: + loop.append(loop[s-1]) + spline.append(spline[s-1]) + camera_check_points.append(camera_check_points[s-1]) + viewer_check_points.append(viewer_check_points[s-1]) + else: + loop.append({key: loops[s][key] for key in loops[s] if key[0] is None and key[1] == layer}) + spline.append({key: splines[s][key] for key in splines[s] if key[0] is None and key[1] == layer}) + camera_check_points.append(check_points) + viewer_check_points.append(vcheck_points) - image_check_points = [] - for s, (_, t, _) in enumerate(scene_keyframes): - check_points = {} - for layer in config.layers: + image_check_points = [] + for s, (_, t, _) in enumerate(scene_keyframes): + check_points = {} state = get_image_state(layer, s) - check_points[layer] = {} for tag in state: - check_points[layer][tag] = {} + check_points[tag] = {} props_use_default = [] at_list = state[tag].get("at_list") - check_points[layer][tag]["at_list"] = [(at_list, t, None)] + check_points[tag]["at_list"] = [(at_list, t, None)] for prop in state[tag]: if not exclusive_check((tag, layer, prop), s): continue if (tag, layer, prop) in all_keyframes[s]: - check_points[layer][tag][prop] = all_keyframes[s][(tag, layer, prop)] + check_points[tag][prop] = all_keyframes[s][(tag, layer, prop)] elif prop in props_groups["focusing"] and prop in camera_check_points[s]: - check_points[layer][tag][prop] = camera_check_points[s][prop] + check_points[tag][prop] = camera_check_points[s][prop] else: if state[tag][prop] is not None: - check_points[layer][tag][prop] = [(get_value((tag, layer, prop), default=False, scene_num=s), t, None)] + check_points[tag][prop] = [(get_value((tag, layer, prop), default=False, scene_num=s), t, None)] elif prop not in not_used_by_default: - check_points[layer][tag][prop] = [(get_value((tag, layer, prop), default=True, scene_num=s), t, None)] + check_points[tag][prop] = [(get_value((tag, layer, prop), default=True, scene_num=s), t, None)] props_use_default.append(prop) - check_points[layer][tag]["props_use_default"] = [(props_use_default, t, None)] + check_points[tag]["props_use_default"] = [(props_use_default, t, None)] #focusing以外のグループプロパティーはここで纏める included_gp = {} - for p in check_points[layer][tag]: - check_result = check_props_group(p, (tag, layer), s) + for p in check_points[tag]: + check_result = check_props_group((tag, layer, p), s) if check_result is not None: gn, ps = check_result if gn != "focusing": args = [] for prop in ps: - args.append(check_points[layer][tag][prop]) + args.append(check_points[tag][prop]) group_cs = [] for cs in zip(*args): v = tuple(c[0] for c in cs) @@ -885,33 +935,33 @@ init -1598 python in _viewers: included_gp[gn] = (ps, group_cs) for gn, (ps, group_cs) in included_gp.items(): for prop in ps: - del check_points[layer][tag][prop] - check_points[layer][tag][gn] = group_cs - if persistent._viewer_focusing and perspective_enabled(s, time=t): - if "blur" in check_points[layer][tag]: - del check_points[layer][tag]["blur"] + del check_points[tag][prop] + check_points[tag][gn] = group_cs + if persistent._viewer_focusing and perspective_enabled(layer, s, time=t): + if "blur" in check_points[tag]: + del check_points[tag]["blur"] else: for p in ("focusing", "dof"): - if p in check_points[layer][tag]: - del check_points[layer][tag][p] - image_check_points.append(check_points) + if p in check_points[tag]: + del check_points[tag][p] + image_check_points.append(check_points) - for css in camera_check_points: - for p in props_groups["focusing"]: - if p in css: - del css[p] - if play: - renpy.show("action_preview", what=Transform(function=renpy.curry(viewer_transform)( - camera_check_points=camera_check_points, image_check_points=image_check_points, scene_checkpoints=deepcopy(scene_keyframes), - viewer_check_points=viewer_check_points, zorder_list=zorder_list, loop=loop, spline=spline, start_time=0., end_time=get_animation_delay()))) - else: - renpy.show("action_preview", what=Transform(function=renpy.curry(viewer_transform)( - camera_check_points=camera_check_points, image_check_points=image_check_points, scene_checkpoints=deepcopy(scene_keyframes), - viewer_check_points=viewer_check_points, zorder_list=zorder_list, loop=loop, spline=spline, time=current_time))) + for css in camera_check_points: + for p in props_groups["focusing"]: + if p in css: + del css[p] + if play: + renpy.show("action_preview_"+layer, what=Transform(function=renpy.curry(viewer_transform)( + camera_check_points=camera_check_points, image_check_points=image_check_points, scene_checkpoints=deepcopy(scene_keyframes), + viewer_check_points=viewer_check_points, zorder_list=zorder_list, loop=loop, spline=spline, start_time=0., end_time=get_animation_delay(), layer=layer))) + else: + renpy.show("action_preview_"+layer, what=Transform(function=renpy.curry(viewer_transform)( + camera_check_points=camera_check_points, image_check_points=image_check_points, scene_checkpoints=deepcopy(scene_keyframes), + viewer_check_points=viewer_check_points, zorder_list=zorder_list, loop=loop, spline=spline, time=current_time, layer=layer))) def viewer_transform(tran, st, at, camera_check_points, image_check_points, scene_checkpoints, viewer_check_points, - zorder_list, loop, spline=None, subpixel=True, time=None, start_time=None, end_time=None): + zorder_list, loop, spline=None, subpixel=True, time=None, start_time=None, end_time=None, layer=None): global current_time, playing if time is None: time = st @@ -929,28 +979,31 @@ init -1598 python in _viewers: if time >= checkpoint: goal = scene_checkpoints[i] if time - checkpoint >= get_transition_delay(goal[0]): + #シーン変移後 child = FixedTimeDisplayable(Transform(function=renpy.curry( camera_transform)(camera_check_points=camera_check_points[i], image_check_points=image_check_points[i], scene_checkpoints=scene_checkpoints, viewer_check_points=viewer_check_points[i], - zorder_list=zorder_list, loop=loop[i], spline=spline[i], subpixel=subpixel, time=time, scene_num=i)), time, at) + zorder_list=zorder_list, loop=loop[i], spline=spline[i], subpixel=subpixel, time=time, scene_num=i, layer=layer)), time, at) else: + #シーン変移中 old_widget = FixedTimeDisplayable(Transform(function=renpy.curry( camera_transform)(camera_check_points=camera_check_points[i-1], image_check_points=image_check_points[i-1], scene_checkpoints=scene_checkpoints, viewer_check_points=viewer_check_points[i-1], - zorder_list=zorder_list, loop=loop[i-1], spline=spline[i-1], subpixel=subpixel, time=time, scene_num=i-1)), time, at) + zorder_list=zorder_list, loop=loop[i-1], spline=spline[i-1], subpixel=subpixel, time=time, scene_num=i-1, layer=layer)), time, at) new_widget = FixedTimeDisplayable(Transform(function=renpy.curry( camera_transform)(camera_check_points=camera_check_points[i], image_check_points=image_check_points[i], scene_checkpoints=scene_checkpoints, viewer_check_points=viewer_check_points[i], - zorder_list=zorder_list, loop=loop[i], spline=spline[i], subpixel=subpixel, time=time, scene_num=i)), time, at) + zorder_list=zorder_list, loop=loop[i], spline=spline[i], subpixel=subpixel, time=time, scene_num=i, layer=layer)), time, at) transition = renpy.python.py_eval("renpy.store."+goal[0]) during_transition_displayable = DuringTransitionDisplayble(transition, old_widget, new_widget, time - checkpoint, 0) child = during_transition_displayable break else: + #スタートシーン child = Transform(function=renpy.curry(camera_transform)( - camera_check_points=camera_check_points[0], image_check_points=image_check_points[0], - scene_checkpoints=scene_checkpoints, viewer_check_points=viewer_check_points[0], - zorder_list=zorder_list, loop=loop[0], spline=spline[0], subpixel=subpixel, time=time, scene_num=0)) + camera_check_points=camera_check_points[-len(scene_checkpoints)], image_check_points=image_check_points[-len(scene_checkpoints)], + scene_checkpoints=scene_checkpoints, viewer_check_points=viewer_check_points[-len(scene_checkpoints)], + zorder_list=zorder_list, loop=loop[-len(scene_checkpoints)], spline=spline[-len(scene_checkpoints)], subpixel=subpixel, time=time, scene_num=-len(scene_checkpoints), layer=layer)) if not persistent._viewer_legacy_gui: if aspect_16_9: box.add(Transform(zoom=preview_size, xpos=(1 - preview_size)/2)(child)) @@ -1010,31 +1063,34 @@ init -1598 python in _viewers: height = renpy.config.screen_height return Transform(align=(.5, .5), matrixtransform=Matrix.offset(width/2, height/2, z11))(camera_model) - def camera_transform(tran, st, at, camera_check_points, image_check_points, scene_checkpoints, viewer_check_points, zorder_list, loop, spline=None, subpixel=True, time=None, scene_num=0): + def camera_transform(tran, st, at, camera_check_points, image_check_points, scene_checkpoints, viewer_check_points, zorder_list, loop, spline=None, subpixel=True, time=None, scene_num=0, layer=None): global third_view_child + image_box = renpy.display.layout.MultiBox(layout='fixed') sideview_image_box = renpy.display.layout.MultiBox(layout='fixed') - for layer in image_check_points: - for tag, zorder in zorder_list[scene_num][layer]: - if tag in image_check_points[layer]: - image_loop = {key[2]+"_loop": loops[scene_num][key] for key in loops[scene_num] if isinstance(key, tuple) and key[0] == tag and key[1] == layer} - image_spline = {key[2]+"_spline": splines[scene_num][key] for key in splines[scene_num] if isinstance(key, tuple) and key[0] == tag and key[1] == layer} - for p in props_groups["focusing"]: - image_loop[p+"_loop"] = loops[scene_num][p] - image_spline[p+"_spline"] = splines[scene_num][p] - image_box.add(Transform(function=renpy.curry(transform)( - check_points=image_check_points[layer][tag], - loop=image_loop, spline=image_spline, - subpixel=subpixel, time=time, scene_num=scene_num, scene_checkpoints=scene_checkpoints))) + for tag, zorder in zorder_list[scene_num][layer]: + if tag in image_check_points: + image_loop = {key[2]+"_loop": loops[scene_num][key] for key in loops[scene_num] if key[0] == tag and key[1] == layer} + image_spline = {key[2]+"_spline": splines[scene_num][key] for key in splines[scene_num] if key[0] == tag and key[1] == layer} + for p in props_groups["focusing"]: + image_loop[p+"_loop"] = loops[scene_num][(None, layer, p)] + image_spline[p+"_spline"] = splines[scene_num][(None, layer, p)] + image_box.add(Transform(function=renpy.curry(transform)( + check_points=image_check_points[tag], + loop=image_loop, spline=image_spline, + subpixel=subpixel, time=time, scene_num=scene_num, scene_checkpoints=scene_checkpoints, layer=layer))) - if persistent._viewer_sideview and scene_num == current_scene and perspective_enabled(scene_num) and not persistent._viewer_legacy_gui: - sideview_image_box.add(Transform(function=renpy.curry(transform)( - check_points=image_check_points[layer][tag], loop=image_loop, spline=image_spline, subpixel=subpixel, - time=time, scene_num=scene_num, scene_checkpoints=scene_checkpoints, side_view=True))) + if persistent._viewer_sideview and len(scene_keyframes)+scene_num == current_scene and perspective_enabled(layer, scene_num) and not persistent._viewer_legacy_gui: + sideview_image_box.add(Transform(function=renpy.curry(transform)( + check_points=image_check_points[tag], loop=image_loop, spline=image_spline, subpixel=subpixel, + time=time, scene_num=scene_num, scene_checkpoints=scene_checkpoints, side_view=True, layer=layer))) - if persistent._viewer_sideview and scene_num == current_scene and perspective_enabled(scene_num) and not persistent._viewer_legacy_gui: - preview_box = renpy.display.layout.MultiBox(layout='fixed') - preview_box.add(sideview_image_box) + camera_loop = {key[2]+"_loop": loop[key] for key in loop if key[1] == layer} + camera_spline = {key[2]+"_spline": spline[key] for key in spline if key[1] == layer} + if persistent._viewer_sideview and len(scene_keyframes)+scene_num == current_scene and perspective_enabled(layer, scene_num) and not persistent._viewer_legacy_gui: + third_view_child[layer] = [] + sideview_box = renpy.display.layout.MultiBox(layout='fixed') + sideview_box.add(sideview_image_box) perspective = camera_check_points["perspective"][0][0] if perspective is True: @@ -1047,35 +1103,33 @@ init -1598 python in _viewers: camera_model = define_camera_model(z11) - # preview_box.add(camera_model) camera_model = Transform(function=renpy.curry(transform)( - check_points=viewer_check_points, loop=loop, spline=spline, subpixel=subpixel, time=time, - scene_num=scene_num, scene_checkpoints=scene_checkpoints, camera=True, side_view=True))(camera_model) + check_points=viewer_check_points, loop=camera_loop, spline=camera_spline, subpixel=subpixel, time=time, + scene_num=scene_num, scene_checkpoints=scene_checkpoints, camera=True, side_view=True, layer=layer))(camera_model) - preview_box.add(camera_model) + sideview_box.add(camera_model) inf = 1000000 - third_view_child = [] for i, r in enumerate(((-90, 0, 0), (0, -90, 0), (0, 0, 0))): - sd = Transform(perspective=(0, inf, inf*10), matrixtransform=Matrix.scale(0.2, 0.2, 1.0)*Matrix.rotate(*r))(preview_box) + sd = Transform(perspective=(0, inf, inf*10), matrixtransform=Matrix.scale(0.2, 0.2, 1.0)*Matrix.rotate(*r))(sideview_box) sd = renpy.store.AlphaMask(sd, Solid("#000")) if aspect_16_9: sd = Transform(zoom=(1 - preview_size)/2, ypos=i*preview_size/3)(sd) else: sd = Transform(zoom=(1 - preview_size)/2, xpos=preview_size, ypos=i*preview_size/3)(sd) - third_view_child.append(sd) + third_view_child[layer].append(sd) camera_box = renpy.display.layout.MultiBox(layout='fixed') #camera position doesn't have effect whithout box camera_box.add(Transform(function=renpy.curry(transform)( - check_points=camera_check_points, loop=loop, spline=spline, - subpixel=subpixel, time=time, camera=True, scene_num=scene_num, scene_checkpoints=scene_checkpoints))(image_box)) + check_points=camera_check_points, loop=camera_loop, spline=camera_spline, + subpixel=subpixel, time=time, camera=True, scene_num=scene_num, scene_checkpoints=scene_checkpoints, layer=layer))(image_box)) tran.set_child(camera_box) return 0 - def transform(tran, st, at, check_points, loop, spline=None, subpixel=True, crop_relative=True, time=None, camera=False, scene_num=None, scene_checkpoints=None, side_view=False): + def transform(tran, st, at, check_points, loop, spline=None, subpixel=True, crop_relative=True, time=None, camera=False, scene_num=None, scene_checkpoints=None, side_view=False, layer=None): # check_points = { prop: [ (value, time, warper).. ] } if subpixel is not None: tran.subpixel = subpixel @@ -1086,7 +1140,7 @@ init -1598 python in _viewers: group_cache = {} sle = renpy.game.context().scene_lists if in_editor and camera and not side_view: - tran.perspective = get_value("perspective", scene_keyframes[scene_num][1], True) + tran.perspective = get_value((None, layer, "perspective"), scene_keyframes[scene_num][1], True) for p, cs in check_points.items(): @@ -1154,10 +1208,10 @@ init -1598 python in _viewers: image_zpos += tran.matrixtransform.zdw camera_zpos = 0 if in_editor: - camera_zpos = get_value("zpos", default=True, scene_num=scene_num) + camera_zpos = get_value((None, layer, "zpos"), default=True, scene_num=scene_num) else: - if "master" in sle.camera_transform: - props = sle.camera_transform["master"] + if layer in sle.camera_transform: + props = sle.camera_transform[layer] if props.zpos: camera_zpos = props.zpos result = camera_blur_amount(image_zpos, camera_zpos, dof, focusing) @@ -1183,10 +1237,10 @@ init -1598 python in _viewers: image_zpos += tran.matrixtransform.zdw camera_zpos = 0 if in_editor: - camera_zpos = get_value("zpos", default=True, scene_num=scene_num) + camera_zpos = get_value((None, layer, "zpos"), default=True, scene_num=scene_num) else: - if "master" in sle.camera_transform: - props = sle.camera_transform["master"] + if layer in sle.camera_transform: + props = sle.camera_transform[layer] if props.zpos: camera_zpos = props.zpos result = camera_blur_amount(image_zpos, camera_zpos, dof, focusing) @@ -1274,8 +1328,8 @@ init -1598 python in _viewers: if in_editor: point_to = getattr(tran, "point_to", None) - perspective = get_value("perspective", scene_keyframes[scene_num][1], True) - if perspective and (isinstance(point_to, renpy.display.transform.Camera) or (side_view and camera)): + perspective = get_value((None, layer, "perspective"), scene_keyframes[scene_num][1], True) + if perspective and (check_version(23032300) and isinstance(point_to, renpy.display.transform.Camera) or (side_view and camera)): if perspective is True: perspective = renpy.config.perspective @@ -1288,9 +1342,9 @@ init -1598 python in _viewers: width = renpy.config.screen_width height = renpy.config.screen_height - placement = (get_value("xpos", default=True, scene_num=scene_num), get_value("ypos", default=True, scene_num=scene_num), get_value("xanchor", default=True, scene_num=scene_num), get_value("yanchor", default=True, scene_num=scene_num), get_value("xoffset", default=True, scene_num=scene_num), get_value("yoffset", default=True, scene_num=scene_num), True) + placement = (get_value((None, layer, "xpos"), default=True, scene_num=scene_num), get_value((None, layer, "ypos"), default=True, scene_num=scene_num), get_value((None, layer, "xanchor"), default=True, scene_num=scene_num), get_value((None, layer, "yanchor"), default=True, scene_num=scene_num), get_value((None, layer, "xoffset"), default=True, scene_num=scene_num), get_value((None, layer, "yoffset"), default=True, scene_num=scene_num), True) xplacement, yplacement = renpy.display.core.place(width, height, width, height, placement) - zpos = get_value("zpos", default=True, scene_num=scene_num) + zpos = get_value((None, layer, "zpos"), default=True, scene_num=scene_num) # direct displayable toward camera if point_to is not None and isinstance(point_to, renpy.display.transform.Camera): @@ -1459,9 +1513,9 @@ init -1598 python in _viewers: mask_prefix_org = file_name[:mask_file_name.find(">")+1] mask_file_name = file_name[mask_file_name.find(">")+1:] - prefix = "".format(time, time) + prefix = "".format(time, time+0.1) if mask_file_name: - mask_prefix = "".format(time, time) + mask_prefix = "".format(time, time+0.1) play = prefix + file_name if mask_file_name: @@ -1469,16 +1523,25 @@ init -1598 python in _viewers: else: mask = None - if name_tuple in movie_cache: - d = movie_cache[name_tuple] - else: - d = deepcopy(d_org) - movie_cache[name_tuple] = d + # if True: + #movie isn't updated with this cache. + # if name_tuple in movie_cache: + # d = movie_cache[name_tuple] + # else: + # d = deepcopy(d_org) + # movie_cache[name_tuple] = d + #this isn't shown correctly if movie is longer than 2 or 3 sec. + d = deepcopy(d_org) d._play = play - d.mask = None - d.loop = False - # d = FixedTimeDisplayable(Movie(play=prefix+file_name, mask=None, loop=False), time, at) + d.mask = mask + d.loop = True + # else: + # pass + #heavy and both isn't shown + # d = Movie(play=play, mask=mask, loop=True) + # d = FixedTimeDisplayable(Movie(play=play, mask=mask, loop=True), time, at) + widget = d # raise Exception((d._play, d.mask)) # elif name_tuple in images: @@ -1497,14 +1560,11 @@ init -1598 python in _viewers: #check exclusive properties if scene_num is None: scene_num = current_scene - if isinstance(key, tuple): - tag, layer, prop = key - state = get_image_state(layer, scene_num) - camera = False + tag, layer, prop = key + if tag is None: + state = camera_state_org[scene_num][layer] else: - prop = key - state = camera_state_org[scene_num] - camera = True + state = get_image_state(layer, scene_num) for set1, set2 in exclusive: if prop in set1 or prop in set2: @@ -1515,19 +1575,13 @@ init -1598 python in _viewers: one_set = set2 other_set = set1 for p in one_set: - if camera: - key2 = p - else: - key2 = (tag, layer, p) + key2 = (tag, layer, p) if key2 in all_keyframes[scene_num]: return True if key2 in state and (state[key2] is not None and state[key2] != get_default(p)): return True for p in other_set: - if camera: - key2 = p - else: - key2 = (tag, layer, p) + key2 = (tag, layer, p) if key2 in all_keyframes[scene_num]: return False if key2 in state and (state[key2] is not None and state[key2] != get_default(p)): @@ -1772,10 +1826,7 @@ init -1598 python in _viewers: def edit_any(key, time=None): if time is None: time = current_time - if isinstance(key, tuple): - prop = key[2] - else: - prop = key + prop = key[2] value = get_value(key, time) if prop in menu_props: global _return @@ -1804,11 +1855,11 @@ init -1598 python in _viewers: def toggle_boolean_property(key): - if isinstance(key, tuple): - tag, layer, prop = key - value_org = get_image_state(layer)[tag][prop] + tag, layer, prop = key + if tag is None: + value_org = camera_state_org[current_scene][layer][key] else: - value_org = camera_state_org[current_scene][key] + value_org = get_image_state(layer)[tag][prop] value = get_value(key, scene_keyframes[current_scene][1], True) #assume default is False if value == value_org or (not value and not value_org): @@ -1818,18 +1869,18 @@ init -1598 python in _viewers: change_time(current_time) - def perspective_enabled(scene_num=None, time=None): + def perspective_enabled(layer, scene_num=None, time=None): if scene_num is None: scene_num = current_scene if time is None: time = scene_keyframes[scene_num][1] - v = get_value("perspective", scene_keyframes[scene_num][1], True, scene_num) + v = get_value((None, layer, "perspective"), scene_keyframes[scene_num][1], True, scene_num) return v or (v is not False and v == 0) def remove_image(layer, tag): def remove_keyframes(layer, tag): - for k in (k for k in all_keyframes[current_scene] if isinstance(k, tuple) and k[0] == tag and k[1] == layer): + for k in (k for k in all_keyframes[current_scene] if k[0] is not None and k[0] == tag and k[1] == layer): del all_keyframes[current_scene][k] renpy.hide(tag, layer) @@ -1848,18 +1899,13 @@ init -1598 python in _viewers: if scene_num is None: scene_num = current_scene - if isinstance(key, tuple): - tag, layer, prop = key - if prop in props_groups["focusing"]: - key = prop - if isinstance(key, tuple): - tag, layer, prop = key - mkey = (tag, layer) - state = get_image_state(layer, scene_num)[tag] + tag, layer, prop = key + if tag is not None and prop in props_groups["focusing"]: + key = (None, layer, prop) + if tag is None: + state = camera_state_org[scene_num][layer] else: - prop = key - mkey = "camera" - state = camera_state_org[scene_num] + state = get_image_state(layer, scene_num)[tag] if key not in all_keyframes[scene_num]: v = state[prop] if v is not None or prop in boolean_props | any_props: @@ -1905,7 +1951,7 @@ init -1598 python in _viewers: complete = 1. if goal[0] is not None or prop in boolean_props | any_props: - check_result = check_props_group(prop, mkey, scene_num) + check_result = check_props_group(key, scene_num) if check_result: gn, ps = check_result @@ -1914,10 +1960,7 @@ init -1598 python in _viewers: new = [] default_value = get_default(prop) for p in ps: - if isinstance(key, tuple): - key2 = (key[0], key[1], p) - else: - key2 = p + key2 = (key[0], key[1], p) old.append(all_keyframes[scene_num][key2][i-1][0]) new.append(all_keyframes[scene_num][key2][i][0]) @@ -1982,7 +2025,15 @@ init -1598 python in _viewers: v = v[index] if isinstance(new, int): - v = int(v) + if check_new_position_type(v): + v = int(v.absolute) + elif isinstance(new, float): + v = int(v) + elif isinstance(new, float): + if check_new_position_type(v): + v = float(v.relative) + elif isinstance(new, int): + v = float(v) return v break else: @@ -1992,20 +2043,21 @@ init -1598 python in _viewers: return cs[0][0] - def put_camera_clipboard(): + def put_camera_clipboard(layer): camera_keyframes = {} - for k in all_keyframes[current_scene]: - if not isinstance(k, tuple) and k != "function": - value = get_value(k, current_time) + for key in all_keyframes[current_scene]: + tag, _, prop = key + if tag is None and prop != "function": + value = get_value(key, current_time) if isinstance(value, float): value = round(value, 3) - elif k in any_props and isinstance(value, str): + elif prop in any_props and isinstance(value, str): value = "'" + value + "'" - camera_keyframes[k] = [(value, 0, None)] - camera_keyframes = set_group_keyframes(camera_keyframes, "camera") + camera_keyframes[prop] = [(value, 0, None)] + camera_keyframes = set_group_keyframes(camera_keyframes, (None, layer, None)) camera_properties = [] - for p in camera_state_org[current_scene]: - check_result = check_props_group(p, "camera") + for p in camera_state_org[current_scene][layer]: + check_result = check_props_group((None, layer, p)) if check_result is not None: gn, ps = check_result if gn not in camera_properties: @@ -2016,7 +2068,9 @@ init -1598 python in _viewers: string = """ camera""" - for p, cs in x_and_y_to_xy([(p, camera_keyframes[p]) for p in camera_properties if p in camera_keyframes]): + if layer != "master": + string += " {layer}".format(layer=layer) + for p, cs in x_and_y_to_xy([(p, camera_keyframes[p]) for p in camera_properties if p in camera_keyframes], layer): if string.find(":") < 0: string += ":\n " string += "{property} {value}".format(property=p, value=cs[0][0]) @@ -2042,19 +2096,19 @@ camera""" def put_image_clipboard(tag, layer): image_keyframes = {} for k in all_keyframes[current_scene]: - if isinstance(k, tuple) and k[0] == tag and k[1] == layer and k[2] != "function": + if k[0] is not None and k[0] == tag and k[1] == layer and k[2] != "function": value = get_value(k, current_time) if isinstance(value, float): value = round(value, 3) elif k[2] in any_props and isinstance(value, str): value = "'" + value + "'" image_keyframes[k[2]] = [(value, 0, None)] - image_keyframes = set_group_keyframes(image_keyframes, (tag, layer)) - if check_focusing_used() and "blur" in image_keyframes: + image_keyframes = set_group_keyframes(image_keyframes, (tag, layer, None)) + if check_focusing_used(layer) and "blur" in image_keyframes: del image_keyframes["blur"] image_properties = [] for p in get_image_state(layer)[tag]: - check_result = check_props_group(p, (tag, layer)) + check_result = check_props_group((tag, layer, p)) if check_result is not None: gn, ps = check_result if gn not in image_properties: @@ -2071,7 +2125,7 @@ show {imagename}""".format(imagename=child) string += " as {tagname}".format(tagname=tag) if layer != "master": string += " onlayer {layer}".format(layer=layer) - for p, cs in x_and_y_to_xy([(p, image_keyframes[p]) for p in image_properties if p in image_keyframes]): + for p, cs in x_and_y_to_xy([(p, image_keyframes[p]) for p in image_properties if p in image_keyframes], layer): if string.find(":") < 0: string += ":\n " string += "{property} {value}".format(property=p, value=cs[0][0]) @@ -2079,9 +2133,9 @@ show {imagename}""".format(imagename=child) string += "\n " else: string += " " - if check_focusing_used(): - focus = get_value("focusing", current_time, True) - dof = get_value("dof", current_time, True) + if check_focusing_used(layer): + focus = get_value((None, layer, "focusing"), current_time, True) + dof = get_value((None, layer, "dof"), current_time, True) result = "function camera_blur({'focusing':[({}, 0, None)], 'dof':[({}, 0, None)]})".format(focus, dof) string += "\n " string += result @@ -2262,22 +2316,24 @@ show {imagename}""".format(imagename=child) camera_state_org.insert(current_scene, {}) zorder_list.insert(current_scene, {}) all_keyframes.insert(current_scene, {}) - for l in config.layers: + for l in get_layers(): image_state[current_scene][l] = {} image_state_org[current_scene][l] = {} + camera_state_org[current_scene][l] = {} zorder_list[current_scene][l] = [] loops.insert(current_scene, defaultdict(_False)) splines.insert(current_scene, defaultdict(dict)) - for i in range(current_scene-1, -1, -1): - if camera_keyframes_exist(i): - break - for p in camera_state_org[i]: - middle_value = get_value(p, scene_keyframes[current_scene][1], False, i) - if isinstance(middle_value, float): - camera_state_org[current_scene][p] = round(middle_value, 2) - else: - camera_state_org[current_scene][p] = middle_value + for layer in get_layers(): + for i in range(current_scene-1, -1, -1): + if camera_keyframes_exist(i, layer): + break + for p in camera_state_org[i][layer]: + middle_value = get_value((None, layer, p), scene_keyframes[current_scene][1], False, i) + if isinstance(middle_value, float): + camera_state_org[current_scene][layer][p] = round(middle_value, 2) + else: + camera_state_org[current_scene][layer][p] = middle_value # if persistent._viewer_legacy_gui: # renpy.show_screen("_action_editor") # elif persistent._open_only_one_page: @@ -2285,9 +2341,9 @@ show {imagename}""".format(imagename=child) renpy.restart_interaction() - def camera_keyframes_exist(scene_num): - for p in camera_state_org[scene_num]: - if p in all_keyframes[scene_num]: + def camera_keyframes_exist(scene_num, layer): + for p in camera_state_org[scene_num][layer]: + if (None, layer, p) in all_keyframes[scene_num]: break else: return False @@ -2309,15 +2365,16 @@ show {imagename}""".format(imagename=child) del loops[scene_num] del splines[scene_num] for s in range(scene_num, len(scene_keyframes)): - for i in range(s, -1, -1): - if camera_keyframes_exist(i): - break - for p in camera_state_org[i]: - middle_value = get_value(p, scene_keyframes[s][1], False, i) - if isinstance(middle_value, float): - camera_state_org[s][p] = round(middle_value, 2) - else: - camera_state_org[s][p] = middle_value + for layer in get_layers(): + for i in range(s, -1, -1): + if camera_keyframes_exist(i, layer): + break + for p in camera_state_org[i][layer]: + middle_value = get_value((None, layer, p), scene_keyframes[s][1], False, i) + if isinstance(middle_value, float): + camera_state_org[s][layer][p] = round(middle_value, 2) + else: + camera_state_org[s][layer][p] = middle_value # if persistent._viewer_legacy_gui: # renpy.show_screen("_action_editor") # elif persistent._open_only_one_page: @@ -2371,15 +2428,16 @@ show {imagename}""".format(imagename=child) scene_keyframes[new_scene_num] = (tran, new, w) for s in range(new_scene_num, len(scene_keyframes)): - for i in range(s, -1, -1): - if camera_keyframes_exist(i): - break - for p in camera_state_org[i]: - middle_value = get_value(p, scene_keyframes[s][1], False, i) - if isinstance(middle_value, float): - camera_state_org[s][p] = round(middle_value, 2) - else: - camera_state_org[s][p] = middle_value + for layer in get_layers(): + for i in range(s, -1, -1): + if camera_keyframes_exist(i, layer): + break + for p in camera_state_org[i][layer]: + middle_value = get_value((None, layer, p), scene_keyframes[s][1], False, i) + if isinstance(middle_value, float): + camera_state_org[s][layer][p] = round(middle_value, 2) + else: + camera_state_org[s][layer][p] = middle_value for k, cs in all_keyframes[new_scene_num].items(): for i, (v, t, w) in enumerate(cs): cs[i] = (v, t - (old - new), w) @@ -2611,25 +2669,16 @@ show {imagename}""".format(imagename=child) def update_gn_spline(key, time, scene_num=None): if scene_num is None: scene_num = current_scene - if isinstance(key, tuple): - tag, layer, prop = key - mkey = (tag, layer) - else: - prop = key - mkey = "camera" + tag, layer, prop = key - check_result = check_props_group(prop, mkey, scene_num) + check_result = check_props_group(key, scene_num) if check_result: gn, ps = check_result pre_knots = [] for p in ps: - if mkey == "camera": - key2 = p - gn_key = gn - else: - key2 = (tag, layer, p) - gn_key = (tag, layer, gn) + key2 = (tag, layer, p) + gn_key = (tag, layer, gn) pre_knots.append(splines[scene_num][key2][time]) knots = [] for knot in zip(*pre_knots): @@ -2640,22 +2689,14 @@ show {imagename}""".format(imagename=child) def add_knot(key, time, default, knot_number=None, recursion=False): if not recursion: - if isinstance(key, tuple): - tag, layer, prop = key - mkey = (tag, layer) - else: - prop = key - mkey = "camera" + tag, layer, prop = key - check_result = check_props_group(prop, mkey, current_scene) + check_result = check_props_group(key, current_scene) if check_result: gn, ps = check_result for p in ps: if p != prop: - if mkey == "camera": - key2 = p - else: - key2 = (tag, layer, p) + key2 = (tag, layer, p) cs = all_keyframes[current_scene][key2] for i, (v, t, w) in enumerate(cs): if t == time: @@ -2677,22 +2718,14 @@ show {imagename}""".format(imagename=child) def remove_knot(key, time, i, recursion=False): if not recursion: - if isinstance(key, tuple): - tag, layer, prop = key - mkey = (tag, layer) - else: - prop = key - mkey = "camera" + tag, layer, prop = key - check_result = check_props_group(prop, mkey, current_scene) + check_result = check_props_group(key, current_scene) if check_result: gn, ps = check_result for p in ps + [gn]: if p != prop: - if mkey == "camera": - key2 = p - else: - key2 = (tag, layer, p) + key2 = (tag, layer, p) remove_knot(key2, time, i, recursion=True) if time in splines[current_scene][key]: @@ -2775,7 +2808,7 @@ show {imagename}""".format(imagename=child) sound_keyframes = {} all_keyframes = [{}] zorder_list = [{}] - for l in config.layers: + for l in get_layers(): zorder_list[current_scene][l] = renpy.get_zorder_list(l) scene_keyframes = [(None, 0, None)] if persistent._viewer_legacy_gui is None: @@ -2857,10 +2890,7 @@ show {imagename}""".format(imagename=child) if delay + scene_start > animation_time: animation_time = delay + scene_start for key, cs in all_keyframes[s].items(): - if isinstance(key, tuple): - prop = key[2] - else: - prop = key + prop = key[2] for (v, t, w) in cs: if prop == "child": delay = get_transition_delay(v[1]) @@ -2889,10 +2919,7 @@ show {imagename}""".format(imagename=child) delay = get_transition_delay(tran) animation_time = delay + scene_start for key, cs in all_keyframes[scene_num].items(): - if isinstance(key, tuple): - prop = key[2] - else: - prop = key + prop = key[2] for (v, t, w) in cs: if prop == "child": delay = get_transition_delay(v[1]) @@ -2902,13 +2929,14 @@ show {imagename}""".format(imagename=child) return animation_time - scene_start - def set_group_keyframes(keyframes, mkey, scene_num=None): + def set_group_keyframes(keyframes, key, scene_num=None): + tag, layer, _ = key result = keyframes.copy() #focusing以外のグループプロパティーはここで纏める included_gp = {} for p in result: - check_result = check_props_group(p, mkey, scene_num) + check_result = check_props_group((tag, layer, p), scene_num) if check_result is not None: gn, ps = check_result if gn != "focusing": @@ -2976,16 +3004,10 @@ show {imagename}""".format(imagename=child) else: same_time_set = [(p, cs)] already_added.append(p) - if layer is not None and tag is not None: - key = (tag, layer, p) - else: - key = p + key = (tag, layer, p) for (p2, cs2) in sorted_list[i+1:]: if p2 not in already_added and len(cs) == len(cs2): - if layer is not None and tag is not None: - key2 = (tag, layer, p2) - else: - key2 = p2 + key2 = (tag, layer, p2) if loops[current_scene][key] != loops[current_scene][key2]: continue for c1, c2 in zip(cs, cs2): @@ -3006,15 +3028,11 @@ show {imagename}""".format(imagename=child) return result - def x_and_y_to_xy(keyframe_list, layer=None, tag=None, check_spline=False, check_loop=False): + def x_and_y_to_xy(keyframe_list, layer, tag=None, check_spline=False, check_loop=False): for xy, (x, y) in xygroup.items(): if x in [p for p, cs in keyframe_list] and y in [p for p, cs in keyframe_list]: - if layer is not None and tag is not None: - xkey = (tag, layer, x) - ykey = (tag, layer, y) - else: - xkey = x - ykey = y + xkey = (tag, layer, x) + ykey = (tag, layer, y) if check_spline and (splines[current_scene][xkey] or splines[current_scene][ykey]): # don't put together when propaerty has spline continue @@ -3053,10 +3071,10 @@ show {imagename}""".format(imagename=child) return [(tag, state[tag]) for tag, _ in zorder] - def check_focusing_used(scene_num = None): + def check_focusing_used(layer, scene_num = None): if scene_num is None: scene_num = current_scene - return (persistent._viewer_focusing and perspective_enabled(scene_num)) + return (persistent._viewer_focusing and perspective_enabled(layer, scene_num)) def put_clipboard(): @@ -3088,91 +3106,99 @@ show {imagename}""".format(imagename=child) files = files[:-4] + "]\n" string += "\n play {} {}".format(channel, files) for s, (scene_tran, scene_start, _) in enumerate(scene_keyframes): - camera_keyframes = {k:v for k, v in all_keyframes[s].items() if not isinstance(k, tuple)} - camera_keyframes = set_group_keyframes(camera_keyframes, "camera", s) - for k, v in camera_keyframes.items(): - if k in any_props: - formated_v = [] - for c in v: - if isinstance(c[0], str): - formated_v.append(("'" + c[0] + "'", c[1], c[2])) - else: - formated_v.append(c) - camera_keyframes[k] = formated_v - camera_properties = [] - for p in camera_state_org[s]: - check_result = check_props_group(p, "camera", s) - if check_result is not None: - gn, ps = check_result - if gn not in camera_properties: - camera_properties.append(gn) - else: - if p not in special_props: - camera_properties.append(p) - if s > 0: - string += """ + for layer in get_layers(): + if s > 0: + string += """ scene""" - - if camera_keyframes: - string += """ - camera: + if layer != "master": + string += " onlayer {}".format(layer) + for layer in get_layers(): + camera_keyframes = {k[2]:v for k, v in all_keyframes[s].items() if k[0] is None and k[1] == layer} + camera_keyframes = set_group_keyframes(camera_keyframes, (None, "master", None), s) + for p, v in camera_keyframes.items(): + if p in any_props: + formated_v = [] + for c in v: + if isinstance(c[0], str): + formated_v.append(("'" + c[0] + "'", c[1], c[2])) + else: + formated_v.append(c) + camera_keyframes[p] = formated_v + camera_properties = [] + for p in camera_state_org[s][layer]: + check_result = check_props_group((None, layer, p), s) + if check_result is not None: + gn, ps = check_result + if gn not in camera_properties: + camera_properties.append(gn) + else: + if p not in special_props: + camera_properties.append(p) + if camera_keyframes: + string += """ + camera""" + if layer == "master": + string += ":" + else: + string += " {layer}:".format(layer=layer) + string += """ subpixel True""" - if "crop" in camera_keyframes: - string += " crop_relative True" - if persistent._one_line_one_prop: - string += "\n " - else: - string += " " - #デフォルトと違っても出力しない方が以前の状態の変化に柔軟だが、 - #xposのような元がNoneやmatrixtransformのような元のマトリックスの順番が違うとアニメーションしない - #rotateは設定されればキーフレームに入り、されてなければ問題ない - #アニメーションしないなら出力しなくてよいのでここでは不要 - for p, cs in x_and_y_to_xy([(p, camera_keyframes[p]) for p in camera_properties if p in camera_keyframes and len(camera_keyframes[p]) == 1]): - string += "{property} {value}".format(property=p, value=cs[0][0]) + if "crop" in camera_keyframes: + string += " crop_relative True" if persistent._one_line_one_prop: string += "\n " else: string += " " - sorted_list = put_prop_togetter(camera_keyframes) - if len(sorted_list): - for same_time_set in sorted_list: - if len(sorted_list) > 1 or loops[s][xy_to_x(sorted_list[0][0][0])] or "function" in camera_keyframes: - add_tab = " " - string += """ - parallel: - """ + #デフォルトと違っても出力しない方が以前の状態の変化に柔軟だが、 + #xposのような元がNoneやmatrixtransformのような元のマトリックスの順番が違うとアニメーションしない + #rotateは設定されればキーフレームに入り、されてなければ問題ない + #アニメーションしないなら出力しなくてよいのでここでは不要 + for p, cs in x_and_y_to_xy([(p, camera_keyframes[p]) for p in camera_properties if p in camera_keyframes and len(camera_keyframes[p]) == 1], layer): + string += "{property} {value}".format(property=p, value=cs[0][0]) + if persistent._one_line_one_prop: + string += "\n " else: - add_tab = "" - string += """ - """ - for p, cs in same_time_set: - string += "{property} {value} ".format(property=p, value=cs[0][0]) - cs = same_time_set[0][1] - for i, c in enumerate(cs[1:]): - if c[2].startswith("warper_generator"): - warper = "warp "+ c[2] - else: - warper = c[2] - string += """ - {tab}{warper} {duration:.2f} """.format(tab=add_tab, warper=warper, duration=cs[i+1][1]-cs[i][1]) - for p2, cs2 in same_time_set: - string += "{property} {value} ".format(property=p2, value=cs2[i+1][0]) - if cs2[i+1][1] in splines[s][xy_to_x(p2)] and splines[s][xy_to_x(p2)][cs2[i+1][1]]: - for knot in splines[s][xy_to_x(p2)][cs2[i+1][1]]: - string += " knot {} ".format(knot) - if loops[s][xy_to_x(p)]: - string += """ - repeat""" - if "function" in camera_keyframes: - for p, cs in camera_keyframes.items(): - if len(cs) > 1: - string += """ + string += " " + sorted_list = put_prop_togetter(camera_keyframes, layer=layer) + if len(sorted_list): + for same_time_set in sorted_list: + if len(sorted_list) > 1 or loops[s][(None, layer, xy_to_x(sorted_list[0][0][0]))] or "function" in camera_keyframes: + add_tab = " " + string += """ parallel: """ - break - else: - string += "\n " - string += "function {} ".format(camera_keyframes["function"][0][0][0]) + else: + add_tab = "" + string += """ + """ + for p, cs in same_time_set: + string += "{property} {value} ".format(property=p, value=cs[0][0]) + cs = same_time_set[0][1] + for i, c in enumerate(cs[1:]): + if c[2].startswith("warper_generator"): + warper = "warp "+ c[2] + else: + warper = c[2] + string += """ + {tab}{warper} {duration:.2f} """.format(tab=add_tab, warper=warper, duration=cs[i+1][1]-cs[i][1]) + for p2, cs2 in same_time_set: + string += "{property} {value} ".format(property=p2, value=cs2[i+1][0]) + if cs2[i+1][1] in splines[s][(None, layer, xy_to_x(p2))] and splines[s][(None, layer, xy_to_x(p2))][cs2[i+1][1]]: + for knot in splines[s][(None, layer, xy_to_x(p2))][cs2[i+1][1]]: + string += " knot {} ".format(knot) + if loops[s][(None, layer, xy_to_x(p))]: + string += """ + repeat""" + if "function" in camera_keyframes: + for p, cs in camera_keyframes.items(): + if len(cs) > 1: + string += """ + parallel: + """ + break + else: + string += "\n " + string += "function {} ".format(camera_keyframes["function"][0][0][0]) for layer in image_state_org[s]: @@ -3180,8 +3206,8 @@ show {imagename}""".format(imagename=child) for tag, _ in zorder_list[s][layer]: if tag not in state: continue - image_keyframes = {k[2]:v for k, v in all_keyframes[s].items() if isinstance(k, tuple) and k[0] == tag and k[1] == layer} - image_keyframes = set_group_keyframes(image_keyframes, (tag, layer), s) + image_keyframes = {k[2]:v for k, v in all_keyframes[s].items() if k[0] is not None and k[0] == tag and k[1] == layer} + image_keyframes = set_group_keyframes(image_keyframes, (tag, layer, None), s) for k, v in image_keyframes.items(): if k in any_props: formated_v = [] @@ -3191,11 +3217,11 @@ show {imagename}""".format(imagename=child) else: formated_v.append(c) image_keyframes[k] = formated_v - if check_focusing_used(s) and "blur" in image_keyframes: + if check_focusing_used(layer, s) and "blur" in image_keyframes: del image_keyframes["blur"] image_properties = [] for p in state[tag]: - check_result = check_props_group(p, (tag, layer), s) + check_result = check_props_group((tag, layer, p), s) if check_result is not None: gn, ps = check_result if gn not in image_properties: @@ -3203,7 +3229,7 @@ show {imagename}""".format(imagename=child) else: if p not in special_props: image_properties.append(p) - if image_keyframes or check_focusing_used(s) or tag in image_state[s][layer]: + if image_keyframes or check_focusing_used(layer, s) or tag in image_state[s][layer]: image_name = state[tag]["child"][0] if "child" in image_keyframes: last_child = image_keyframes["child"][-1][0][0] @@ -3237,7 +3263,7 @@ show {imagename}""".format(imagename=child) string += " " sorted_list = put_prop_togetter(image_keyframes, layer, tag) if "child" in image_keyframes: - if len(sorted_list) >= 1 or loops[s][(tag, layer, "child")] or check_focusing_used(s) or "function" in image_keyframes: + if len(sorted_list) >= 1 or loops[s][(tag, layer, "child")] or check_focusing_used(layer, s) or "function" in image_keyframes: add_tab = " " string += """ parallel:""" @@ -3285,7 +3311,7 @@ show {imagename}""".format(imagename=child) if len(sorted_list): for same_time_set in sorted_list: if len(sorted_list) > 1 or loops[s][(tag, layer, xy_to_x(sorted_list[0][0][0]))] \ - or "child" in image_keyframes or check_focusing_used(s) or "function" in image_keyframes: + or "child" in image_keyframes or check_focusing_used(layer, s) or "function" in image_keyframes: add_tab = " " string += """ parallel: @@ -3312,7 +3338,7 @@ show {imagename}""".format(imagename=child) if loops[s][(tag,layer,xy_to_x(p))]: string += """ repeat""" - if check_focusing_used(s) or "function" in image_keyframes: + if check_focusing_used(layer, s) or "function" in image_keyframes: for p, cs in image_keyframes.items(): if len(cs) > 1 or "child" in image_keyframes: string += """ @@ -3321,15 +3347,15 @@ show {imagename}""".format(imagename=child) break else: string += "\n " - if check_focusing_used(s): + if check_focusing_used(layer, s): focusing_cs = {"focusing":[(get_default("focusing"), 0, None)], "dof":[(get_default("dof"), 0, None)]} for p in props_groups["focusing"]: - if p in all_keyframes[s]: - focusing_cs[p] = [(v, t-scene_start, w) for (v, t, w) in all_keyframes[s][p]] - if loops[s]["focusing"] or loops[s]["dof"]: + if (None, layer, p) in all_keyframes[s]: + focusing_cs[p] = [(v, t-scene_start, w) for (v, t, w) in all_keyframes[s][(None, layer, p)]] + if loops[s][(None, layer, "focusing")] or loops[s][(None, layer, "dof")]: focusing_loop = {} - focusing_loop["focusing_loop"] = loops[s]["focusing"] - focusing_loop["dof_loop"] = loops[s]["dof"] + focusing_loop["focusing_loop"] = loops[s][(None, layer, "focusing")] + focusing_loop["dof_loop"] = loops[s][(None, layer, "dof")] focusing_func_string = "camera_blur({}, {})".format(focusing_cs, focusing_loop) else: focusing_func_string = "camera_blur({})".format(focusing_cs) @@ -3365,64 +3391,69 @@ show {imagename}""".format(imagename=child) if times: string += "\n stop {}".format(channel) - for i in range(-1, -len(scene_keyframes)-1, -1): - if camera_keyframes_exist(i): - break - last_camera_scene = i - camera_keyframes = {k:v for k, v in all_keyframes[last_camera_scene].items() if not isinstance(k, tuple)} - for p in camera_state_org[last_camera_scene]: - if p not in camera_keyframes: - if camera_state_org[last_camera_scene][p] is not None and camera_state_org[last_camera_scene][p] != camera_state_org[0][p]: - camera_keyframes[p] = [(camera_state_org[last_camera_scene][p], scene_keyframes[last_camera_scene][1], None)] - camera_keyframes = set_group_keyframes(camera_keyframes, "camera", last_camera_scene) - for k, v in camera_keyframes.items(): - if k in any_props: - formated_v = [] - for c in v: - if isinstance(c[0], str): - formated_v.append(("'" + c[0] + "'", c[1], c[2])) - else: - formated_v.append(c) - camera_keyframes[k] = formated_v - if [cs for cs in camera_keyframes.values() if len(cs) > 1]: - string += """ - camera:""" - for p, cs in camera_keyframes.items(): - if len(cs) > 1 and loops[last_camera_scene][p]: - string += """ - animation""" + for layer in get_layers(): + for i in range(-1, -len(scene_keyframes)-1, -1): + if camera_keyframes_exist(i, layer): break - first = True - for p, cs in x_and_y_to_xy(sort_props(camera_keyframes), check_loop=True): - if p not in special_props: - if len(cs) > 1 and not loops[last_camera_scene][xy_to_x(p)]: - if first: - first = False - string += """ - """ - string += "{property} {value}".format(property=p, value=cs[-1][0]) - if persistent._one_line_one_prop: - string += "\n " + last_camera_scene = i + camera_keyframes = {k[2]:v for k, v in all_keyframes[last_camera_scene].items() if k[0] is None and k[1] == layer} + for p in camera_state_org[last_camera_scene][layer]: + if p not in camera_keyframes: + if camera_state_org[last_camera_scene][layer][p] is not None and camera_state_org[last_camera_scene][layer][p] != camera_state_org[0][layer][p]: + camera_keyframes[p] = [(camera_state_org[last_camera_scene][layer][p], scene_keyframes[last_camera_scene][1], None)] + camera_keyframes = set_group_keyframes(camera_keyframes, (None, layer, None), last_camera_scene) + for p, v in camera_keyframes.items(): + if p in any_props: + formated_v = [] + for c in v: + if isinstance(c[0], str): + formated_v.append(("'" + c[0] + "'", c[1], c[2])) else: - string += " " - - for p, cs in sort_props(camera_keyframes): - if p not in special_props: - if len(cs) > 1 and loops[last_camera_scene][p]: + formated_v.append(c) + camera_keyframes[p] = formated_v + if [cs for cs in camera_keyframes.values() if len(cs) > 1]: + string += """ + camera""" + if layer == "master": + string += ":" + else: + string += " {}:".format(layer) + for p, cs in camera_keyframes.items(): + if len(cs) > 1 and loops[last_camera_scene][(None, layer, p)]: string += """ + animation""" + break + first = True + for p, cs in x_and_y_to_xy(sort_props(camera_keyframes), layer, check_loop=True): + if p not in special_props: + if len(cs) > 1 and not loops[last_camera_scene][(None, layer, xy_to_x(p))]: + if first: + first = False + string += """ + """ + string += "{property} {value}".format(property=p, value=cs[-1][0]) + if persistent._one_line_one_prop: + string += "\n " + else: + string += " " + + for p, cs in sort_props(camera_keyframes): + if p not in special_props: + if len(cs) > 1 and loops[last_camera_scene][(None, layer, p)]: + string += """ parallel: {property} {value}""".format(property=p, value=cs[0][0]) - for i, c in enumerate(cs[1:]): - if c[2].startswith("warper_generator"): - warper = "warp "+ c[2] - else: - warper = c[2] - string += """ + for i, c in enumerate(cs[1:]): + if c[2].startswith("warper_generator"): + warper = "warp "+ c[2] + else: + warper = c[2] + string += """ {warper} {duration:.2f} {property} {value}""".format(warper=warper, duration=cs[i+1][1]-cs[i][1], property=p, value=c[0]) - if c[1] in splines[last_camera_scene][p] and splines[last_camera_scene][p][c[1]]: - for knot in splines[last_camera_scene][p][c[1]]: - string += " knot {}".format(knot) - string += """ + if c[1] in splines[last_camera_scene][(None, layer, p)] and splines[last_camera_scene][(None, layer, p)][c[1]]: + for knot in splines[last_camera_scene][(None, layer, p)][c[1]]: + string += " knot {}".format(knot) + string += """ repeat""" # if "function" in camera_keyframes: @@ -3444,8 +3475,8 @@ show {imagename}""".format(imagename=child) for tag, _ in zorder_list[last_scene][layer]: if tag not in state: continue - image_keyframes = {k[2]:v for k, v in all_keyframes[last_scene].items() if isinstance(k, tuple) and k[0] == tag and k[1] == layer} - image_keyframes = set_group_keyframes(image_keyframes, (tag, layer), last_scene) + image_keyframes = {k[2]:v for k, v in all_keyframes[last_scene].items() if k[0] is not None and k[0] == tag and k[1] == layer} + image_keyframes = set_group_keyframes(image_keyframes, (tag, layer, None), last_scene) for k, v in image_keyframes.items(): if k in any_props: formated_v = [] @@ -3455,7 +3486,7 @@ show {imagename}""".format(imagename=child) else: formated_v.append(c) image_keyframes[k] = formated_v - if check_focusing_used(last_scene) and "blur" in image_keyframes: + if check_focusing_used(layer, last_scene) and "blur" in image_keyframes: del image_keyframes["blur"] if not image_keyframes: @@ -3568,13 +3599,13 @@ show {imagename}""".format(imagename=child) string += """ repeat""" - if check_focusing_used(last_scene):# or "function" in image_keyframes: + if check_focusing_used(layer, last_scene):# or "function" in image_keyframes: # if check_focusing_used(last_scene): focusing_cs = {"focusing":[(get_default("focusing"), 0, None)], "dof":[(get_default("dof"), 0, None)]} - if "focusing" in all_keyframes[last_scene]: - focusing_cs["focusing"] = all_keyframes[last_scene]["focusing"] - if "dof" in all_keyframes[last_scene]: - focusing_cs["dof"] = all_keyframes[last_scene]["dof"] + if (None, layer, "focusing") in all_keyframes[last_scene]: + focusing_cs["focusing"] = all_keyframes[last_scene][(None, layer, "focusing")] + if (None, layer, "dof") in all_keyframes[last_scene]: + focusing_cs["dof"] = all_keyframes[last_scene][(None, layer, "dof")] if len(focusing_cs["focusing"]) > 1 or len(focusing_cs["dof"]) > 1: for p, cs in sort_props(image_keyframes): if p not in special_props: @@ -3591,14 +3622,14 @@ show {imagename}""".format(imagename=child) else: string += """ """ - if not loops[last_scene]["focusing"]: + if not loops[last_scene][(None, layer, "focusing")]: focusing_cs["focusing"] = [focusing_cs["focusing"][-1]] if not loops[last_scene]["dof"]: focusing_cs["dof"] = [focusing_cs["dof"][-1]] - if loops[last_scene]["focusing"] or loops[last_scene]["dof"]: + if loops[last_scene][(None, layer, "focusing")] or loops[last_scene][(None, layer, "dof")]: focusing_loop = {} - focusing_loop["focusing_loop"] = loops[last_scene]["focusing"] - focusing_loop["dof_loop"] = loops[last_scene]["dof"] + focusing_loop["focusing_loop"] = loops[last_scene][(None, layer, "focusing")] + focusing_loop["dof_loop"] = loops[last_scene][(None, layer, "dof")] focusing_func_string = "camera_blur({}, {})".format(focusing_cs, focusing_loop) else: focusing_func_string = "camera_blur({})".format(focusing_cs) diff --git a/game/dev/actioneditor/ActionEditor_config.rpy b/game/dev/actioneditor/ActionEditor_config.rpy index 8c2412e..fae4d8e 100644 --- a/game/dev/actioneditor/ActionEditor_config.rpy +++ b/game/dev/actioneditor/ActionEditor_config.rpy @@ -64,6 +64,8 @@ init -1600 python in _viewers: default_channel_list = ["sound"] #default side view. default_sideview = True + #Not included layers + not_included_layer = ("transient", "screens", "overlay") default_graphic_editor_narrow_range = 2. default_graphic_editor_wide_range = 2000 diff --git a/game/dev/actioneditor/ActionEditor_screens.rpy b/game/dev/actioneditor/ActionEditor_screens.rpy index 98aa24e..5e0e142 100644 --- a/game/dev/actioneditor/ActionEditor_screens.rpy +++ b/game/dev/actioneditor/ActionEditor_screens.rpy @@ -1,13 +1,13 @@ -screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mode=[]): - default layer = "master" +screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mode=[], layer="master"): python: if _viewers.at_clauses_flag: renpy.notify(_("ActionEditor can't show images with 'at clause'")) _viewers.at_clauses_flag = False - int_format = "{:> }" + int_format = "{:> .0f}" float_format = "{:> .2f}" + pos_format = "(abs={:> .0f}, rel={:> .2f})" generate_changed = _viewers.generate_changed get_value = _viewers.get_value @@ -31,6 +31,7 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo DraggableValue = _viewers.DraggableValue TimeLine = _viewers.TimeLine perspective_enabled = _viewers.perspective_enabled + check_new_position_type = _viewers.check_new_position_type if opened is None: opened = {} @@ -40,60 +41,70 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo indent = " " - xpos, ypos = get_value("xpos", default=True), get_value("ypos", default=True) + xpos, ypos = get_value((None, layer, "xpos"), default=True), get_value((None, layer, "ypos"), default=True) xmove_amount1 = 0.1 ymove_amount1 = 0.1 xmove_amount2 = 0.3 ymove_amount2 = 0.3 - xvalue_range = yvalue_range = persistent._wide_range if isinstance(xpos, int): xmove_amount1 = int(xmove_amount1*config.screen_width) xmove_amount2 = int(xmove_amount2*config.screen_width) - else: + xvalue_range = persistent._wide_range + elif isinstance(xpos, float): xvalue_range = persistent._narrow_range + elif check_new_position_type(xpos): + xmove_amount1 = renpy.atl.position.from_any(int(xmove_amount1*config.screen_width)) + xmove_amount2 = renpy.atl.position.from_any(int(xmove_amount2*config.screen_width)) + xvalue_range = renpy.atl.position.from_any(persistent._wide_range) + if isinstance(ypos, int): ymove_amount1 = int(ymove_amount1*config.screen_height) ymove_amount2 = int(ymove_amount2*config.screen_height) - else: + yvalue_range = persistent._wide_range + elif isinstance(ypos, float): yvalue_range = persistent._narrow_range + elif check_new_position_type(ypos): + ymove_amount1 = renpy.atl.position.from_any(int(ymove_amount1*config.screen_height)) + ymove_amount2 = renpy.atl.position.from_any(int(ymove_amount2*config.screen_height)) + yvalue_range = renpy.atl.position.from_any(persistent._wide_range) key "hide_windows" action NullAction() - if get_value("perspective", scene_keyframes[0][1], True) is not False: + if get_value((None, layer, "perspective"), scene_keyframes[0][1], True) is not False: if _viewers.fps_keymap: - key "s" action Function(generate_changed("ypos"), ypos + ymove_amount1 + yvalue_range) - key "w" action Function(generate_changed("ypos"), ypos - ymove_amount1 + yvalue_range) - key "a" action Function(generate_changed("xpos"), xpos - xmove_amount1 + xvalue_range) - key "d" action Function(generate_changed("xpos"), xpos + xmove_amount1 + xvalue_range) - key "S" action Function(generate_changed("ypos"), ypos + ymove_amount2 + yvalue_range) - key "W" action Function(generate_changed("ypos"), ypos - ymove_amount2 + yvalue_range) - key "A" action Function(generate_changed("xpos"), xpos - xmove_amount2 + xvalue_range) - key "D" action Function(generate_changed("xpos"), xpos + xmove_amount2 + xvalue_range) + key "s" action Function(generate_changed((None, layer, "ypos")), ypos + ymove_amount1 + yvalue_range) + key "w" action Function(generate_changed((None, layer, "ypos")), ypos - ymove_amount1 + yvalue_range) + key "a" action Function(generate_changed((None, layer, "xpos")), xpos - xmove_amount1 + xvalue_range) + key "d" action Function(generate_changed((None, layer, "xpos")), xpos + xmove_amount1 + xvalue_range) + key "S" action Function(generate_changed((None, layer, "ypos")), ypos + ymove_amount2 + yvalue_range) + key "W" action Function(generate_changed((None, layer, "ypos")), ypos - ymove_amount2 + yvalue_range) + key "A" action Function(generate_changed((None, layer, "xpos")), xpos - xmove_amount2 + xvalue_range) + key "D" action Function(generate_changed((None, layer, "xpos")), xpos + xmove_amount2 + xvalue_range) else: - key "j" action Function(generate_changed("ypos"), ypos + ymove_amount1 + yvalue_range) - key "k" action Function(generate_changed("ypos"), ypos - ymove_amount1 + yvalue_range) - key "h" action Function(generate_changed("xpos"), xpos - xmove_amount1 + xvalue_range) - key "l" action Function(generate_changed("xpos"), xpos + xmove_amount1 + xvalue_range) - key "J" action Function(generate_changed("ypos"), ypos + ymove_amount2 + yvalue_range) - key "K" action Function(generate_changed("ypos"), ypos - ymove_amount2 + yvalue_range) - key "H" action Function(generate_changed("xpos"), xpos - xmove_amount2 + xvalue_range) - key "L" action Function(generate_changed("xpos"), xpos + xmove_amount2 + xvalue_range) - if perspective_enabled(): - key "rollback" action Function(generate_changed("zpos"), get_value("zpos", default=True)+100+persistent._wide_range) - key "rollforward" action Function(generate_changed("zpos"), get_value("zpos", default=True)-100+persistent._wide_range) + key "j" action Function(generate_changed((None, layer, "ypos")), ypos + ymove_amount1 + yvalue_range) + key "k" action Function(generate_changed((None, layer, "ypos")), ypos - ymove_amount1 + yvalue_range) + key "h" action Function(generate_changed((None, layer, "xpos")), xpos - xmove_amount1 + xvalue_range) + key "l" action Function(generate_changed((None, layer, "xpos")), xpos + xmove_amount1 + xvalue_range) + key "J" action Function(generate_changed((None, layer, "ypos")), ypos + ymove_amount2 + yvalue_range) + key "K" action Function(generate_changed((None, layer, "ypos")), ypos - ymove_amount2 + yvalue_range) + key "H" action Function(generate_changed((None, layer, "xpos")), xpos - xmove_amount2 + xvalue_range) + key "L" action Function(generate_changed((None, layer, "xpos")), xpos + xmove_amount2 + xvalue_range) + if perspective_enabled(layer): + key "rollback" action Function(generate_changed((None, layer, "zpos")), get_value((None, layer, "zpos"), default=True)+100+persistent._wide_range) + key "rollforward" action Function(generate_changed((None, layer, "zpos")), get_value((None, layer, "zpos"), default=True)-100+persistent._wide_range) if time: - timer time+_viewers.return_margin action [Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode), \ + timer time+_viewers.return_margin action [Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode, layer=layer), \ Function(_viewers.return_start_time, previous_time)] - key "game_menu" action [Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode), \ + key "game_menu" action [Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode, layer=layer), \ Function(change_time, previous_time)] $play_action = [SensitiveIf(get_sorted_keyframes(current_scene) or len(scene_keyframes) > 1), SelectedIf(time > 0), \ _viewers.pause, \ - Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode)] + Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode, layer=layer)] else: key "game_menu" action Confirm("Close Editor?", Return()) $play_action = [SensitiveIf(get_sorted_keyframes(current_scene) or len(scene_keyframes) > 1), SelectedIf(time > 0), \ [If(get_sorted_keyframes(current_scene) or len(scene_keyframes) > 1, Function(_viewers.play, play=True))], \ - Show("_new_action_editor", opened=opened, time=_viewers.get_animation_delay(), previous_time=current_time, in_graphic_mode=in_graphic_mode)] + Show("_new_action_editor", opened=opened, time=_viewers.get_animation_delay(), previous_time=current_time, in_graphic_mode=in_graphic_mode, layer=layer)] if persistent._show_camera_icon: if _viewers.aspect_16_9: @@ -102,7 +113,7 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo else: $xpos = 0 $ypos = 0 - add _viewers.ImagePins() xpos xpos ypos ypos + add _viewers.ImagePins(layer) xpos xpos ypos ypos key "K_SPACE" action play_action key "action_editor" action NullAction() @@ -173,29 +184,29 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo xalign 1. yalign .5 style "new_action_editor_bar" if in_graphic_mode: $key = in_graphic_mode[0] - if isinstance(key, tuple): - $p = key[2] - $tag=(key[0], key[1]) + if key[0] is None: + $kind = "camera" else: - $p = key - $tag = "camera" + $kind = "image" $value = get_value(key, default=True) $f = generate_changed(key) $use_wide_range = is_wide_range(key) if isinstance(value, float): $value_format = float_format - else: + elif isinstance(value, int): $value_format = int_format + elif check_new_position_type(value): + $value_format = pos_format hbox: #他と同時にグラフィックモードで表示するとタイムバーが反応しないことがある hbox: style_group "new_action_editor_c" textbutton " [key]" action None text_color "#FFF" - add DraggableValue(value_format, key, f, is_force_plus(p)) + add DraggableValue(value_format, key, f, is_force_plus(key[2])) fixed: # ysize int(config.screen_height*(1-_viewers.preview_size)-_viewers.time_column_height) ysize None - add TimeLine(current_scene, tag, key=key, changed=f, opened=opened, in_graphic_mode=in_graphic_mode) + add TimeLine(current_scene, kind, key=key, changed=f, opened=opened, in_graphic_mode=in_graphic_mode) else: viewport: mousewheel True @@ -217,268 +228,288 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo textbutton "- "+"scene[s]" action SelectedIf(current_scene == s) fixed: add TimeLine(s, None) - if "camera" not in opened[s]: - hbox: + for l in _viewers.get_layers(): + if l != layer: hbox: - style_group "new_action_editor_c" - if persistent._open_only_one_page: - $new_opened = {s:["camera"]} - else: - $new_opened = opened.copy() - $new_opened[s] = new_opened[s] + ["camera"] - textbutton _(indent+"+ "+"camera"): - action [SensitiveIf(get_value("perspective", scene_keyframes[s][1], True) is not False), - Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode)] - fixed: - add TimeLine(s, "camera") - else: - hbox: + hbox: + style_group "new_action_editor_c" + textbutton _(indent+"+ "+l): + action Show("_new_action_editor", opened=opened, in_graphic_mode=in_graphic_mode, layer=l) + # fixed: + # add TimeLine(s, "camera", key=(None, layer, None)) + else: hbox: - style_group "new_action_editor_c" - $new_opened = opened.copy() - $new_opened[s] = opened[s].copy() - $new_opened[s].remove("camera") - textbutton _(indent+"- "+"camera"): - action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) - textbutton _("clipboard"): - action Function(_viewers.put_camera_clipboard) - size_group None - style_group "new_action_editor_b" - fixed: - add TimeLine(s, "camera") - for props_set_name, props_set in props_sets: - if props_set_name in opened[s]: + hbox: + style_group "new_action_editor_c" + textbutton _(indent+"- "+l): + action SelectedIf(l == layer) + # fixed: + # add TimeLine(s, None) + if (None, layer, None) not in opened[s]: + hbox: + hbox: + style_group "new_action_editor_c" + if persistent._open_only_one_page: + $new_opened = {s:[(None, layer, None)]} + else: + $new_opened = opened.copy() + $new_opened[s] = new_opened[s] + [(None, layer, None)] + textbutton _(indent*2+"+ "+"camera"): + action [SensitiveIf(get_value((None, layer, "perspective"), scene_keyframes[s][1], True) is not False), + Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer)] + fixed: + add TimeLine(s, "camera", key=(None, layer, None)) + else: hbox: hbox: style_group "new_action_editor_c" $new_opened = opened.copy() $new_opened[s] = opened[s].copy() - $new_opened[s].remove(props_set_name) - textbutton indent*2+"- " + props_set_name action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) + $new_opened[s].remove((None, layer, None)) + textbutton _(indent*2+"- "+"camera"): + action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) + textbutton _("clipboard"): + action Function(_viewers.put_camera_clipboard, layer=layer) + size_group None + style_group "new_action_editor_b" fixed: - add TimeLine(s, "camera", props_set=props_set) - for p in _viewers.expand_props_set(props_set, "camera", s): - $key = p - $value = get_value(p, default=True) - $f = generate_changed(p) - $use_wide_range = is_wide_range(key, s) - if isinstance(value, float): - $value_format = float_format - else: - $value_format = int_format - $shown_p = p - if p.count("_") == 3: - $sign, num1, num2, p2 = p.split("_") - if sign in ("matrixtransform", "matrixcolor"): - $shown_p = p2 - hbox: - if p == "perspective": + add TimeLine(s, "camera", key=(None, layer, None)) + for props_set_name, props_set in props_sets: + if (None, layer, props_set_name) in opened[s]: + hbox: hbox: style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - textbutton "[value]": - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.edit_any, p, time=scene_keyframes[current_scene][1])] - size_group None - elif p == "function": - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - textbutton "[value[0]]": - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.edit_function, key)] - size_group None - elif p in _viewers.any_props: - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - if isinstance(value, str): - $caption = "'[value]'" - else: - $caption = "[value]" - textbutton caption: - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.edit_any, key)] - size_group None - elif p in _viewers.boolean_props: - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - textbutton "[value]": - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.toggle_boolean_property, key)] - size_group None - else: - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [shown_p]" action None text_color "#FFF" - add DraggableValue(value_format, key, f, is_force_plus(p)) - # if key not in in_graphic_mode: - if p not in _viewers.boolean_props | {"function", "perspective"}: + $new_opened = opened.copy() + $new_opened[s] = opened[s].copy() + $new_opened[s].remove((None, layer, props_set_name)) + textbutton indent*3+"- " + props_set_name action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) fixed: - add TimeLine(s, "camera", key=key, changed=f, opened=opened) - # else: - # fixed: - # ysize int(config.screen_height*(1-_viewers.preview_size)-_viewers.time_column_height) - # add TimeLine(s, "camera", key=key, changed=f, use_wide_range=use_wide_range, opened=opened, in_graphic_mode=in_graphic_mode) - else: - hbox: - hbox: - style_group "new_action_editor_c" - if persistent._open_only_one_page: - $new_opened = {s:["camera", props_set_name]} - else: - $new_opened = opened.copy() - $new_opened[s] = new_opened[s] + [props_set_name] - textbutton indent*2+"+ "+props_set_name action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) - fixed: - add TimeLine(s, "camera", props_set=props_set) - for tag in tag_list: - if tag not in opened[s]: - hbox: - hbox: - style_group "new_action_editor_c" - if persistent._open_only_one_page: - $new_opened = {s:[tag]} + add TimeLine(s, "camera", key=(None, layer, None), props_set=props_set) + for p in _viewers.expand_props_set(props_set, (None, layer, None), s): + $key = (None, layer, p) + $value = get_value(key, default=True) + $f = generate_changed(key) + $use_wide_range = is_wide_range(key, s) + if isinstance(value, float): + $value_format = float_format + elif isinstance(value, int): + $value_format = int_format + elif check_new_position_type(value): + $value_format = pos_format + $shown_p = p + if p.count("_") == 3: + $sign, num1, num2, p2 = p.split("_") + if sign in ("matrixtransform", "matrixcolor"): + $shown_p = p2 + hbox: + if p == "perspective": + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + textbutton "[value]": + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.edit_any, key, time=scene_keyframes[current_scene][1])] + size_group None + elif p == "function": + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + textbutton "[value[0]]": + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.edit_function, key)] + size_group None + elif p in _viewers.any_props: + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + if isinstance(value, str): + $caption = "'[value]'" + else: + $caption = "[value]" + textbutton caption: + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.edit_any, key)] + size_group None + elif p in _viewers.boolean_props: + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + textbutton "[value]": + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.toggle_boolean_property, key)] + size_group None + else: + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [shown_p]" action None text_color "#FFF" + add DraggableValue(value_format, key, f, is_force_plus(p)) + # if key not in in_graphic_mode: + if p not in _viewers.boolean_props | {"function", "perspective"}: + fixed: + add TimeLine(s, "camera", key=key, changed=f, opened=opened) + # else: + # fixed: + # ysize int(config.screen_height*(1-_viewers.preview_size)-_viewers.time_column_height) + # add TimeLine(s, "camera", key=key, changed=f, use_wide_range=use_wide_range, opened=opened, in_graphic_mode=in_graphic_mode) else: - $new_opened = opened.copy() - $new_opened[s] = new_opened[s] + [tag] - textbutton indent+"+ "+"{}".format(tag): - action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) - fixed: - add TimeLine(s, (tag, layer)) - else: - hbox: - hbox: - style_group "new_action_editor_c" - $new_opened = opened.copy() - $new_opened[s] = opened[s].copy() - $new_opened[s].remove(tag) - textbutton indent+"- "+"{}".format(tag): - action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) - textbutton _("clipboard"): - action Function(_viewers.put_image_clipboard, tag, layer) - style_group "new_action_editor_b" - size_group None - fixed: - add TimeLine(s, (tag, layer)) - for props_set_name, props_set in props_sets: - if (tag, layer, props_set_name) not in opened[s]: + hbox: + hbox: + style_group "new_action_editor_c" + if persistent._open_only_one_page: + $new_opened = {s:[(None, layer, None), (None, layer, props_set_name)]} + else: + $new_opened = opened.copy() + $new_opened[s] = new_opened[s] + [(None, layer, props_set_name)] + textbutton indent*3+"+ "+props_set_name action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) + fixed: + add TimeLine(s, "camera", key=(None, layer, None), props_set=props_set) + for tag in tag_list: + if (tag, layer, None) not in opened[s]: hbox: hbox: style_group "new_action_editor_c" if persistent._open_only_one_page: - $new_opened = {s:[tag, (tag, layer, props_set_name)]} + $new_opened = {s:[(tag, layer, None)]} else: $new_opened = opened.copy() - $new_opened[s] = new_opened[s] + [(tag, layer, props_set_name)] - textbutton indent*2+"+ "+props_set_name: - action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) + $new_opened[s] = new_opened[s] + [(tag, layer, None)] + textbutton indent*2+"+ "+"{}".format(tag): + action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) fixed: - add TimeLine(s, (tag, layer), props_set=props_set) + add TimeLine(s, "image", key=(tag, layer, None)) else: hbox: hbox: style_group "new_action_editor_c" $new_opened = opened.copy() $new_opened[s] = opened[s].copy() - $new_opened[s].remove((tag, layer, props_set_name)) - textbutton indent*2+"- " + props_set_name: - action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) + $new_opened[s].remove((tag, layer, None)) + textbutton indent*2+"- "+"{}".format(tag): + action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) + textbutton _("clipboard"): + action Function(_viewers.put_image_clipboard, tag, layer) + style_group "new_action_editor_b" + size_group None fixed: - add TimeLine(s, (tag, layer), props_set=props_set) - for p in _viewers.expand_props_set(props_set, (tag, layer), s): - $key = (tag, layer, p) - $value = get_value(key, default=True) - $f = generate_changed(key) - $use_wide_range = is_wide_range(key, s) - if isinstance(value, float): - $value_format = float_format - else: - $value_format = int_format - $shown_p = p - if p.count("_") == 3: - $sign, num1, num2, p2 = p.split("_") - if sign in ("matrixtransform", "matrixcolor"): - $shown_p = p2 - hbox: - if p == "child": - vbox: - xfill False - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [value[0]]": - action [SelectedIf(keyframes_exist((tag, layer, "child"))), - Function(_viewers.change_child, tag, layer, default=value[0])] - size_group None - text_language "unicode" - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" with [value[1]]": - action [ - SelectedIf(keyframes_exist((tag, layer, "child"))), - Function(_viewers.edit_transition, tag, layer)] - size_group None - text_language "unicode" - elif p == "function": + add TimeLine(s, "image", key=(tag, layer, None)) + for props_set_name, props_set in props_sets: + if (tag, layer, props_set_name) not in opened[s]: + hbox: hbox: style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - textbutton "[value[0]]": - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.edit_function, key)] - size_group None - elif p in _viewers.any_props: - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - if isinstance(value, str): - $caption = "'[value]'" + if persistent._open_only_one_page: + $new_opened = {s:[(tag, layer, None), (tag, layer, props_set_name)]} else: - $caption = "[value]" - textbutton caption: - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.edit_any, key)] - size_group None - elif p in _viewers.boolean_props: - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [p]": - action None text_color "#FFF" - textbutton "[value]": - action [SelectedIf(keyframes_exist(key)), - Function(_viewers.toggle_boolean_property, key)] - size_group None - else: - hbox: - style_group "new_action_editor_c" - textbutton indent*3+" [shown_p]": - action None text_color "#FFF" - add DraggableValue(value_format, key, f, is_force_plus(p)) - if p not in _viewers.boolean_props | {"function", "perspective"}: + $new_opened = opened.copy() + $new_opened[s] = new_opened[s] + [(tag, layer, props_set_name)] + textbutton indent*3+"+ "+props_set_name: + action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) fixed: - # if key not in in_graphic_mode: - add TimeLine(s, (tag, layer), key=key, changed=f, opened=opened) - # else: - # ysize int(config.screen_height*(1-_viewers.preview_size)-_viewers.time_column_height) - # add TimeLine(s, (tag, layer), key=key, changed=f, use_wide_range=use_wide_range, opened=opened, in_graphic_mode=in_graphic_mode) - $new_opened = opened.copy() - $new_opened[s] = opened[s].copy() - $new_opened[s] = [o for o in opened if (not isinstance(o, tuple) or o[0] != tag) and o !=tag] - textbutton _(indent*3+" remove"): - action [SensitiveIf(tag in _viewers.image_state[s][layer]), - Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode), - Function(_viewers.remove_image, layer, tag)] - size_group None - textbutton _(indent+"+(add image)"): - action Function(_viewers.add_image, layer) - style_group "new_action_editor_c" + add TimeLine(s, "image", key=(tag, layer, None), props_set=props_set) + else: + hbox: + hbox: + style_group "new_action_editor_c" + $new_opened = opened.copy() + $new_opened[s] = opened[s].copy() + $new_opened[s].remove((tag, layer, props_set_name)) + textbutton indent*3+"- " + props_set_name: + action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) + fixed: + add TimeLine(s, "image", key=(tag, layer, None), props_set=props_set) + for p in _viewers.expand_props_set(props_set, (tag, layer, None), s): + $key = (tag, layer, p) + $value = get_value(key, default=True) + $f = generate_changed(key) + $use_wide_range = is_wide_range(key, s) + if isinstance(value, float): + $value_format = float_format + elif isinstance(value, int): + $value_format = int_format + elif check_new_position_type(value): + $value_format = pos_format + $shown_p = p + if p.count("_") == 3: + $sign, num1, num2, p2 = p.split("_") + if sign in ("matrixtransform", "matrixcolor"): + $shown_p = p2 + hbox: + if p == "child": + vbox: + xfill False + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [value[0]]": + action [SelectedIf(keyframes_exist((tag, layer, "child"))), + Function(_viewers.change_child, tag, layer, default=value[0])] + size_group None + text_language "unicode" + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" with [value[1]]": + action [ + SelectedIf(keyframes_exist((tag, layer, "child"))), + Function(_viewers.edit_transition, tag, layer)] + size_group None + text_language "unicode" + elif p == "function": + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + textbutton "[value[0]]": + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.edit_function, key)] + size_group None + elif p in _viewers.any_props: + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + if isinstance(value, str): + $caption = "'[value]'" + else: + $caption = "[value]" + textbutton caption: + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.edit_any, key)] + size_group None + elif p in _viewers.boolean_props: + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [p]": + action None text_color "#FFF" + textbutton "[value]": + action [SelectedIf(keyframes_exist(key)), + Function(_viewers.toggle_boolean_property, key)] + size_group None + else: + hbox: + style_group "new_action_editor_c" + textbutton indent*4+" [shown_p]": + action None text_color "#FFF" + add DraggableValue(value_format, key, f, is_force_plus(p)) + if p not in _viewers.boolean_props | {"function", "perspective"}: + fixed: + # if key not in in_graphic_mode: + add TimeLine(s, "image", key=key, changed=f, opened=opened) + # else: + # ysize int(config.screen_height*(1-_viewers.preview_size)-_viewers.time_column_height) + # add TimeLine(s, (tag, layer), key=key, changed=f, use_wide_range=use_wide_range, opened=opened, in_graphic_mode=in_graphic_mode) + $new_opened = opened.copy() + $new_opened[s] = opened[s].copy() + $new_opened[s] = [o for o in opened if not isinstance(o, tuple) or o[0] != tag] + textbutton _(indent*4+" remove"): + action [ Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer), + Function(_viewers.remove_image, layer, tag)] + size_group None + textbutton _(indent*2+"+(add image)"): + action Function(_viewers.add_image, layer) + style_group "new_action_editor_c" textbutton _("+(add scene)"): action _viewers.add_scene style_group "new_action_editor_c" @@ -493,7 +524,7 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo $new_opened["sounds"] = True textbutton _("+ "+"sounds"): action [SensitiveIf(persistent._viewer_channel_list), - Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode)] + Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer)] fixed: add TimeLine(s, "sounds") else: @@ -501,8 +532,8 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo style_group "new_action_editor_c" $new_opened = opened.copy() $del new_opened["sounds"] - textbutton _(indent+"- "+"sounds"): - action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode) + textbutton _(indent*2+"- "+"sounds"): + action Show("_new_action_editor", opened=new_opened, in_graphic_mode=in_graphic_mode, layer=layer) textbutton _("clipboard"): action Function(_viewers.put_sound_clipboard) size_group None @@ -511,7 +542,7 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo hbox: hbox: style_group "new_action_editor_c" - textbutton indent*1+" [channel]": + textbutton indent*2+" [channel]": action None text_color "#FFF" size_group None fixed: @@ -522,7 +553,7 @@ screen _new_action_editor(opened=None, time=0, previous_time=None, in_graphic_mo for t in sorted_play_times: if current_time >= t: $value = sound_keyframes[channel][t] - textbutton indent*2+" [value]": + textbutton indent*3+" [value]": action [SelectedIf(keyframes_exist(channel, is_sound=True)), Function(_viewers.edit_playing_file, channel, current_time)] size_group None @@ -637,8 +668,9 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): if _viewers.at_clauses_flag: renpy.notify(_("ActionEditor can't show images with 'at clause'")) _viewers.at_clauses_flag = False - int_format = "{:> }" + int_format = "{:> .0f}" float_format = "{:> .2f}" + pos_format = "(abs={:> .0f}, rel={:> .2f})" generate_changed = _viewers.generate_changed get_value = _viewers.get_value get_default = _viewers.get_default @@ -656,12 +688,13 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): props_groups = _viewers.props_groups keyframes_exist = _viewers.keyframes_exist perspective_enabled = _viewers.perspective_enabled + check_new_position_type = _viewers.check_new_position_type play_action = [SensitiveIf(get_sorted_keyframes(current_scene) or len(scene_keyframes) > 1), \ SelectedIf(False), Function(_viewers.play, play=True), \ Show("_action_editor", tab=tab, layer=layer, opened=opened, page=page, time=_viewers.get_animation_delay())] - xpos, ypos = get_value("xpos", default=True), get_value("ypos", default=True) + xpos, ypos = get_value((None, layer, "xpos"), default=True), get_value((None, layer, "ypos"), default=True) xmove_amount1 = 0.1 ymove_amount1 = 0.1 xmove_amount2 = 0.3 @@ -681,28 +714,28 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): key "K_SPACE" action play_action key "action_editor" action NullAction() key "hide_windows" action NullAction() - if get_value("perspective", scene_keyframes[0][1], True) is not False: + if get_value((None, layer, "perspective"), scene_keyframes[0][1], True) is not False: if _viewers.fps_keymap: - key "s" action Function(generate_changed("ypos"), ypos + ymove_amount1 + yvalue_range) - key "w" action Function(generate_changed("ypos"), ypos - ymove_amount1 + yvalue_range) - key "a" action Function(generate_changed("xpos"), xpos - xmove_amount1 + xvalue_range) - key "d" action Function(generate_changed("xpos"), xpos + xmove_amount1 + xvalue_range) - key "S" action Function(generate_changed("ypos"), ypos + ymove_amount2 + yvalue_range) - key "W" action Function(generate_changed("ypos"), ypos - ymove_amount2 + yvalue_range) - key "A" action Function(generate_changed("xpos"), xpos - xmove_amount2 + xvalue_range) - key "D" action Function(generate_changed("xpos"), xpos + xmove_amount2 + xvalue_range) + key "s" action Function(generate_changed((None, layer, "ypos")), ypos + ymove_amount1 + yvalue_range) + key "w" action Function(generate_changed((None, layer, "ypos")), ypos - ymove_amount1 + yvalue_range) + key "a" action Function(generate_changed((None, layer, "xpos")), xpos - xmove_amount1 + xvalue_range) + key "d" action Function(generate_changed((None, layer, "xpos")), xpos + xmove_amount1 + xvalue_range) + key "S" action Function(generate_changed((None, layer, "ypos")), ypos + ymove_amount2 + yvalue_range) + key "W" action Function(generate_changed((None, layer, "ypos")), ypos - ymove_amount2 + yvalue_range) + key "A" action Function(generate_changed((None, layer, "xpos")), xpos - xmove_amount2 + xvalue_range) + key "D" action Function(generate_changed((None, layer, "xpos")), xpos + xmove_amount2 + xvalue_range) else: - key "j" action Function(generate_changed("ypos"), ypos + ymove_amount1 + yvalue_range) - key "k" action Function(generate_changed("ypos"), ypos - ymove_amount1 + yvalue_range) - key "h" action Function(generate_changed("xpos"), xpos - xmove_amount1 + xvalue_range) - key "l" action Function(generate_changed("xpos"), xpos + xmove_amount1 + xvalue_range) - key "J" action Function(generate_changed("ypos"), ypos + ymove_amount2 + yvalue_range) - key "K" action Function(generate_changed("ypos"), ypos - ymove_amount2 + yvalue_range) - key "H" action Function(generate_changed("xpos"), xpos - xmove_amount2 + xvalue_range) - key "L" action Function(generate_changed("xpos"), xpos + xmove_amount2 + xvalue_range) - if perspective_enabled(): - key "rollback" action Function(generate_changed("zpos"), get_value("zpos", default=True)+100+persistent._wide_range) - key "rollforward" action Function(generate_changed("zpos"), get_value("zpos", default=True)-100+persistent._wide_range) + key "j" action Function(generate_changed(None, layer, "ypos"), ypos + ymove_amount1 + yvalue_range) + key "k" action Function(generate_changed(None, layer, "ypos"), ypos - ymove_amount1 + yvalue_range) + key "h" action Function(generate_changed(None, layer, "xpos"), xpos - xmove_amount1 + xvalue_range) + key "l" action Function(generate_changed(None, layer, "xpos"), xpos + xmove_amount1 + xvalue_range) + key "J" action Function(generate_changed(None, layer, "ypos"), ypos + ymove_amount2 + yvalue_range) + key "K" action Function(generate_changed(None, layer, "ypos"), ypos - ymove_amount2 + yvalue_range) + key "H" action Function(generate_changed(None, layer, "xpos"), xpos - xmove_amount2 + xvalue_range) + key "L" action Function(generate_changed(None, layer, "xpos"), xpos + xmove_amount2 + xvalue_range) + if perspective_enabled(layer): + key "rollback" action Function(generate_changed((None, layer, "zpos")), get_value((None, layer, "zpos"), default=True)+100+persistent._wide_range) + key "rollforward" action Function(generate_changed((None, layer, "zpos")), get_value((None, layer, "zpos"), default=True)-100+persistent._wide_range) if time: timer time+1 action [Show("_action_editor", tab=tab, layer=layer, opened=opened, page=page), \ @@ -724,7 +757,7 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): else: page_list.append(state_list) state=_viewers.get_image_state(layer) - if get_value("perspective", scene_keyframes[current_scene][1], True) is False and tab == "camera": + if get_value((None, layer, "perspective"), scene_keyframes[current_scene][1], True) is False and tab == "camera": tab = state_list[0] frame: @@ -759,14 +792,20 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): for i, ks in enumerate(all_keyframes): textbutton "[i]" action [SelectedIf(current_scene == i), Function(_viewers.change_scene, i), Show("_action_editor")] textbutton _("+") action _viewers.add_scene + hbox: + style_group "action_editor_a" + xfill False + for l in _viewers.get_layers(): + textbutton "{}".format(l): + action [SelectedIf(l == layer), Show("_action_editor", layer=l, page=page)] hbox: style_group "action_editor_a" xfill False textbutton _("<"): action [SensitiveIf(page != 0), Show("_action_editor", tab=tab, layer=layer, page=page-1), renpy.restart_interaction] textbutton _("camera"): - action [SensitiveIf(get_value("perspective", scene_keyframes[current_scene][1], True) is not False), - SelectedIf(tab == "camera"), Show("_action_editor", tab="camera")] + action [SensitiveIf(get_value((None, layer, "perspective"), scene_keyframes[current_scene][1], True) is not False), + SelectedIf(tab == "camera"), Show("_action_editor", tab="camera", layer=layer)] for n in page_list[page]: textbutton "{}".format(n): action [SelectedIf(n == tab), Show("_action_editor", tab=n, layer=layer, page=page)] @@ -780,7 +819,7 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): xfill False xalign 1. if tab == "camera": - textbutton _("clipboard") action Function(_viewers.put_camera_clipboard) size_group None + textbutton _("clipboard") action Function(_viewers.put_camera_clipboard, layer=layer) size_group None # textbutton _("reset") action [_viewers.camera_reset, renpy.restart_interaction] size_group None else: textbutton _("remove") action [ @@ -799,10 +838,11 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): for i, (props_set_name, props_set) in enumerate(props_sets): if i == opened: textbutton "- " + props_set_name action [SelectedIf(True), NullAction()] - for p in _viewers.expand_props_set(props_set, "camera", current_scene): - $value = get_value(p, default=True) - $f = generate_changed(p) - $use_wide_range = is_wide_range(p) + for p in _viewers.expand_props_set(props_set, (None, layer, None), current_scene): + $key = (None, layer, p) + $value = get_value(key, default=True) + $f = generate_changed(key) + $use_wide_range = is_wide_range(key) if use_wide_range: $value_range = persistent._wide_range $bar_page = 1 @@ -811,8 +851,10 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): $bar_page = .05 if isinstance(value, float): $value_format = float_format - else: + elif isinstance(value, int): $value_format = int_format + elif check_new_position_type(value): + $value_format = pos_format $shown_p = p if p.count("_") == 3: $sign, num1, num2, p2 = p.split("_") @@ -820,39 +862,44 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): $shown_p = p2 hbox: textbutton " [shown_p]": - action [SensitiveIf(p in all_keyframes[current_scene]), - SelectedIf(keyframes_exist(p)), Show("_edit_keyframe", key=p, change_func=f)] + action [SensitiveIf((None, layer, p) in all_keyframes[current_scene]), + SelectedIf(keyframes_exist(key)), Show("_edit_keyframe", key=key, change_func=f)] if p == "perspective": textbutton "[value]": - action [SelectedIf(get_value(p, scene_keyframes[current_scene][1], True)), - Function(_viewers.edit_any, p, time=scene_keyframes[current_scene][1])] + action [SelectedIf(get_value(key, scene_keyframes[current_scene][1], True)), + Function(_viewers.edit_any, key, time=scene_keyframes[current_scene][1])] elif p == "function": textbutton "[value[0]]": - action [SelectedIf(get_value(p, scene_keyframes[current_scene][1], True)), - Function(_viewers.edit_function, p)] + action [SelectedIf(get_value(key, scene_keyframes[current_scene][1], True)), + Function(_viewers.edit_function, key)] elif p in _viewers.any_props: if isinstance(value, str): $caption = "'[value]'" else: $caption = "[value]" textbutton caption: - action [SelectedIf(get_value(p, scene_keyframes[current_scene][1], True)), - Function(_viewers.edit_any, p)] + action [SelectedIf(get_value(key, scene_keyframes[current_scene][1], True)), + Function(_viewers.edit_any, key)] elif p in _viewers.boolean_props: textbutton "[value]": - action [SelectedIf(get_value(p, scene_keyframes[current_scene][1], True)), - Function(_viewers.toggle_boolean_property, p)] + action [SelectedIf(get_value(key, scene_keyframes[current_scene][1], True)), + Function(_viewers.toggle_boolean_property, key)] else: - if is_force_plus(p): - $bar_value = value - else: - $bar_value = value + value_range - $value_range = value_range*2 - textbutton value_format.format(value): - action Function(edit_value, f, use_wide_range=use_wide_range, default=value, force_plus=is_force_plus(p)) - alternate Function(reset, p) style_group "action_editor_b" - bar adjustment ui.adjustment(range=value_range, value=bar_value, page=bar_page, changed=f): - xalign 1. yalign .5 style "action_editor_bar" + if isinstance(value, (int, float)): + if is_force_plus(p): + $bar_value = value + else: + $bar_value = value + value_range + $value_range = value_range*2 + textbutton value_format.format(value): + action Function(edit_value, f, use_wide_range=use_wide_range, default=value, force_plus=is_force_plus(p)) + alternate Function(reset, key) style_group "action_editor_b" + bar adjustment ui.adjustment(range=value_range, value=bar_value, page=bar_page, changed=f): + xalign 1. yalign .5 style "action_editor_bar" + elif check_new_position_type(value): + textbutton value_format.format(value.absolute, value.relative): + action Function(edit_value, f, use_wide_range=use_wide_range, default=value, force_plus=is_force_plus(p)) + alternate Function(reset, key) style_group "action_editor_b" else: hbox: textbutton "+ "+props_set_name: @@ -861,7 +908,7 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): for i, (props_set_name, props_set) in enumerate(props_sets): if i == opened: textbutton "- " + props_set_name action [SelectedIf(True), NullAction()] - for p in _viewers.expand_props_set(props_set, (tab, layer), current_scene): + for p in _viewers.expand_props_set(props_set, (tab, layer, None), current_scene): $key = (tab, layer, p) $value = get_value(key, default=True) $f = generate_changed(key) @@ -874,8 +921,10 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): $bar_page = .05 if isinstance(value, float): $value_format = float_format - else: + elif isinstance(value, int): $value_format = int_format + elif check_new_position_type(value): + $value_format = pos_format $shown_p = p if p.count("_") == 3: $sign, num1, num2, p2 = p.split("_") @@ -914,16 +963,21 @@ screen _action_editor(tab="camera", layer="master", opened=0, time=0, page=0): action [SelectedIf(get_value(key, scene_keyframes[current_scene][1], True)), Function(_viewers.toggle_boolean_property, key)] else: - if is_force_plus(p): - $bar_value = value - else: - $bar_value = value + value_range - $value_range = value_range*2 - textbutton value_format.format(value): - action Function(edit_value, f, use_wide_range=use_wide_range, default=value, force_plus=is_force_plus(p)) - alternate Function(reset, key) style_group "action_editor_b" - bar adjustment ui.adjustment(range=value_range, value=bar_value, page=bar_page, changed=f): - xalign 1. yalign .5 style "action_editor_bar" + if isinstance(value, (int, float)): + if is_force_plus(p): + $bar_value = value + else: + $bar_value = value + value_range + $value_range = value_range*2 + textbutton value_format.format(value): + action Function(edit_value, f, use_wide_range=use_wide_range, default=value, force_plus=is_force_plus(p)) + alternate Function(reset, key) style_group "action_editor_b" + bar adjustment ui.adjustment(range=value_range, value=bar_value, page=bar_page, changed=f): + xalign 1. yalign .5 style "action_editor_bar" + elif check_new_position_type(value): + textbutton value_format.format(value.absolute, value.relative): + action Function(edit_value, f, use_wide_range=use_wide_range, default=value, force_plus=is_force_plus(p)) + alternate Function(reset, key) style_group "action_editor_b" else: hbox: textbutton "+ "+props_set_name: @@ -973,6 +1027,8 @@ screen _input_screen(message=_("type value"), default=""): key "game_menu" action Return("") if isinstance(default, float): $default = round(default, 2) + elif default is None: + $default = "None" frame: style_group "action_editor_input" @@ -1006,7 +1062,7 @@ screen _action_editor_option(): text _("(*This doesn't work correctly when the animation include loops and that tag is already shown)") textbutton _("skippable") action [SelectedIf(persistent._viewer_allow_skip), ToggleField(persistent, "_viewer_allow_skip")] text _("Enable/Disable simulating camera blur(This is available when perspective is True)") - textbutton _("focusing") action [SensitiveIf(_viewers.get_value("perspective", _viewers.scene_keyframes[_viewers.current_scene][1], True)), SelectedIf(persistent._viewer_focusing), ToggleField(persistent, "_viewer_focusing"), Function(_viewers.change_time, _viewers.current_time)] + textbutton _("focusing") action [SensitiveIf(_viewers.get_value((None, "master", "perspective"), _viewers.scene_keyframes[_viewers.current_scene][1], True)), SelectedIf(persistent._viewer_focusing), ToggleField(persistent, "_viewer_focusing"), Function(_viewers.change_time, _viewers.current_time)] text _("One line includes only one property in clipboard data") textbutton _("one_line_one_property") action [ToggleField(persistent, "_one_line_one_prop")] text _("Assign default warper") @@ -1113,31 +1169,17 @@ screen _value_menu(prop, default): screen _edit_keyframe(key, change_func=None): $check_points = _viewers.all_keyframes[_viewers.current_scene][key] $use_wide_range=_viewers.is_wide_range(key) - if isinstance(key, tuple): - $n, l, p = key - $mkey = (n, l) - $k_list = [key] - $check_points_list = [check_points] - $loop_button_action = [ToggleDict(_viewers.loops[_viewers.current_scene], key)] - $check_result = _viewers.check_props_group(p, mkey) - if check_result is not None: - $gn, ps = check_result + $check_result = _viewers.check_props_group(key) + $n, l, p = key + $k_list = [key] + $check_points_list = [check_points] + $loop_button_action = [ToggleDict(_viewers.loops[_viewers.current_scene], key)] + if check_result is not None: + $gn, ps = check_result + if n is not None or gn != "focusing": $k_list = [(n, l, p) for p in ps] $check_points_list = [_viewers.all_keyframes[_viewers.current_scene][k2] for k2 in k_list] $loop_button_action = [ToggleDict(_viewers.loops[_viewers.current_scene], k2) for k2 in k_list+[(n, l, gn)]] - else: - $p = key - $mkey = "camera" - $k_list = [key] - $check_points_list = [check_points] - $loop_button_action = [ToggleDict(_viewers.loops[_viewers.current_scene], key)] - $check_result = _viewers.check_props_group(key, mkey) - if check_result is not None: - $gn, ps = check_result - if gn != "focusing": - $k_list = ps - $check_points_list = [_viewers.all_keyframes[_viewers.current_scene][k2] for k2 in k_list] - $loop_button_action = [ToggleDict(_viewers.loops[_viewers.current_scene], k2) for k2 in k_list+[gn]] modal True key "game_menu" action Hide("_edit_keyframe") @@ -1156,7 +1198,7 @@ screen _edit_keyframe(key, change_func=None): textbutton "[v[1]]" action Function(_viewers.edit_transition, n, l, time=t) size_group None else: textbutton _("{}".format(w)) action None - if _viewers.check_props_group(p, mkey) is None: + if _viewers.check_props_group(key) is None: textbutton _("spline") action None textbutton _("{}".format(v)) action [\ Function(_viewers.edit_value, change_func, default=v, use_wide_range=use_wide_range, force_plus=_viewers.is_force_plus(p), time=t), \ @@ -1171,7 +1213,7 @@ screen _edit_keyframe(key, change_func=None): textbutton "[v[1]]" action Function(_viewers.edit_transition, n, l, time=t) size_group None else: textbutton _("{}".format(w)) action Function(_viewers.edit_warper, check_points=check_points_list, old=t, value_org=w) - if (_viewers.check_props_group(p, mkey) is None and p not in _viewers.disallow_spline) or (_viewers.check_props_group(p, mkey)[0] not in _viewers.disallow_spline): + if (_viewers.check_props_group(key) is None and p not in _viewers.disallow_spline) or (_viewers.check_props_group(key)[0] not in _viewers.disallow_spline): textbutton _("spline") action [\ SelectedIf(t in _viewers.splines[_viewers.current_scene][key]), \ Show("_spline_editor", change_func=change_func, \ @@ -1300,18 +1342,21 @@ init 1 python in _viewers: def get_sideview(st, at, i): - d = getattr(renpy.store._viewers, "third_view_child", Null()) - if isinstance(d, list): - d = d[i] - return (d, 0) + d = getattr(renpy.store._viewers, "third_view_child", None) + box = Fixed() + if d: + for layer in get_layers(): + if layer in d and d[layer]: + box.add(d[layer][i]) + return (box, 0) - def expand_props_set(props_set, tag, scene_num): + def expand_props_set(props_set, key, scene_num): + tag, layer, _ = key rv = [] - if tag == "camera": - state = camera_state_org[scene_num] + if tag is None: + state = camera_state_org[scene_num][layer] else: - tag, layer = tag state = get_image_state(layer, scene_num)[tag] for p in props_set: @@ -1324,19 +1369,17 @@ init 1 python in _viewers: matrixargs.append(prop) rv.extend(matrixargs) else: - if tag == "camera": - if p not in state: - continue + if p not in state: + continue + if tag is None: if p == "child": continue - if p in props_groups["focusing"] and (not persistent._viewer_focusing or not perspective_enabled(scene_num)): + if p in props_groups["focusing"] and (not persistent._viewer_focusing or not perspective_enabled(layer, scene_num)): continue else: - if p not in state: - continue if p in props_groups["focusing"]: continue - elif persistent._viewer_focusing and perspective_enabled(scene_num) and p == "blur": + elif persistent._viewer_focusing and perspective_enabled(layer, scene_num) and p == "blur": continue rv.append(p) return rv @@ -1375,7 +1418,7 @@ init 1 python in _viewers: return pos - def pos_to_value(y, use_wide_range, force_plus): + def pos_to_value(y, use_wide_range, force_plus, key=None): if use_wide_range: range = persistent._graphic_editor_wide_range else: @@ -1394,14 +1437,30 @@ init 1 python in _viewers: value = -range if value > range: value = range + if key is not None: + if isinstance(get_value(key, default=True), float): + value = float(value) + elif isinstance(get_value(key, default=True), int): + value = int(value) + elif check_new_position_type(get_value(key, default=True)): + value = int(value) return value - def value_to_pos(value, range, force_plus): + def value_to_pos(value, range, force_plus, key=None): barheight = config.screen_height*(1-preview_size)-time_column_height if force_plus: frac = value/float(range) else: + if isinstance(value, (int, float)): + pass + elif check_new_position_type(value): + n, l, p = key + if p in ("xpos", "xanchor"): + scale = config.screen_width + elif p in ("ypos", "yanchor"): + scale = config.screen_height + value = get_absolute_from_pos(value, scale) frac = value/float(range)*0.5 + 0.5 pos = barheight - key_ysize - frac*(barheight - 3*key_half_ysize) if pos > barheight - key_ysize: @@ -1417,7 +1476,7 @@ init 1 python in _viewers: range = persistent._graphic_editor_wide_range else: range = persistent._graphic_editor_narrow_range - return value_to_pos(value, range, force_plus) + return value_to_pos(value, range, force_plus, key) def key_drag_changed(pos, key, time, is_sound=False, in_graphic_mode=None): @@ -1427,31 +1486,33 @@ init 1 python in _viewers: x = pos key_list = [key] if not is_sound: - if isinstance(key, tuple): - n, l, p = key - check_result = check_props_group(p, (n, l)) - if check_result is not None: - _, ps = check_result + n, l, p = key + check_result = check_props_group(key) + if check_result is not None: + gn, ps = check_result + if n is not None or gn != "focusing": key_list = [(n, l, p) for p in ps] - else: - p = key - check_result = check_props_group(p, "camera") - if check_result is not None: - _, ps = check_result - if gn != "focusing": - key_list = [p for p in ps] goal = pos_to_time(x) if move_keyframe(new=goal, old=time, keys=key_list, is_sound=is_sound): time = goal if in_graphic_mode: use_wide_range = is_wide_range(key) - value = pos_to_value(y, use_wide_range, is_force_plus(p)) + value = pos_to_value(y, use_wide_range, is_force_plus(p), key) vchanged = generate_changed(key) vchanged(to_changed_value(value, is_force_plus(p), use_wide_range), time) return time + def get_absolute_from_pos(pos, scale): + if isinstance(pos, int): + return pos + elif isinstance(pos, float): + return int(pos * scale) + elif check_new_position_type(pos): + return int(pos.absolute + pos.relative * scale) + + def absolute_pos(st, at): (x, y) = renpy.get_mouse_pos() if aspect_16_9: @@ -1542,7 +1603,10 @@ init 1 python in _viewers: style = self.hover_text_style else: style = self.text_style - d = Text(self.format.format(value), align=(.5, .5), style=style) + if isinstance(value, (int, float)): + d = Text(self.format.format(value), align=(.5, .5), style=style) + elif check_new_position_type(value): + d = Text(self.format.format(value.absolute, value.relative), align=(.5, .5), style=style) box = Fixed() box.add(d) render = box.render(width, height, st, at) @@ -1559,7 +1623,10 @@ init 1 python in _viewers: if ev.type == self.MOUSEMOTION and self.clicking: self.dragging = True - v = ((x - self.last_x)*self.change_per_pix)*self.speed+self.value + if isinstance(self.value, (int, float)): + v = ((x - self.last_x)*self.change_per_pix)*self.speed+self.value + elif check_new_position_type(self.value): + v = renpy.atl.position.from_any(int(((x - self.last_x)*self.change_per_pix)*self.speed))+self.value self.changed(to_changed_value(v, self.force_plus, self.use_wide_range)) self.hovered = False @@ -1607,16 +1674,16 @@ init 1 python in _viewers: class TimeLine(renpy.Displayable): - def __init__(self, scene, tag, props_set=None, key=None, changed=None, opened=None, in_graphic_mode=[], use_wide_range=None): + def __init__(self, scene, kind, props_set=None, key=None, changed=None, opened=None, in_graphic_mode=[], use_wide_range=None): super(TimeLine, self).__init__() from pygame import MOUSEMOTION from renpy.store import Function, Solid, Fixed self.scene = scene - self.tag = tag + self.kind = kind self.props_set = props_set self.key = key self.changed=changed - self.use_wide_range = is_wide_range(key) if key is not None and use_wide_range else None + self.use_wide_range = is_wide_range(key) if key is not None and key[2] is not None and use_wide_range else None self.opened=opened self.in_graphic_mode = in_graphic_mode @@ -1633,7 +1700,7 @@ init 1 python in _viewers: return False if self.scene != other.scene: return False - if self.tag != other.tag: + if self.kind != other.kind: return False if self.props_set != other.props_set: return False @@ -1651,38 +1718,35 @@ init 1 python in _viewers: box = Fixed() box.add(self.background.get_child()) - if self.tag is None: + if self.kind is None: _, t, _ = scene_keyframes[self.scene] child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=None, clicked=Function(change_time, t)) new_children.append(child) for key, cs in all_keyframes[self.scene].items(): - if isinstance(key, tuple): - p = key[2] - else: - p = key + p = key[2] if p not in props_groups["focusing"] or \ - (persistent._viewer_focusing and perspective_enabled(self.scene)): + (persistent._viewer_focusing and perspective_enabled(key[1], self.scene)): for c in cs: _, t, _ = c child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=None, clicked=Function(change_time, t)) new_children.append(child) - elif self.tag == "camera" and self.props_set is None and self.key is None: - for p in camera_state_org[self.scene]: + elif self.kind == "camera" and self.props_set is None and self.key[2] is None: + for p in camera_state_org[self.scene][self.key[1]]: _all_keyframes = all_keyframes[self.scene] if (p not in props_groups["focusing"] or - (persistent._viewer_focusing and perspective_enabled(self.scene))): - for _, t, _ in _all_keyframes.get(p, []): + (persistent._viewer_focusing and perspective_enabled(self.key[1], self.scene))): + for _, t, _ in _all_keyframes.get((None, self.key[1], p), []): child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=None, clicked=Function(change_time, t)) new_children.append(child) - elif self.tag == "camera" and self.props_set is not None: + elif self.kind == "camera" and self.props_set is not None: _all_keyframes = all_keyframes[self.scene] for p in self.props_set: if (p not in props_groups["focusing"] or \ - (persistent._viewer_focusing and perspective_enabled(self.scene))): - for _, t, _ in _all_keyframes.get(p, []): + (persistent._viewer_focusing and perspective_enabled(self.key[1], self.scene))): + for _, t, _ in _all_keyframes.get((None, self.key[1], p), []): child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=None, clicked=Function(change_time, t)) new_children.append(child) - elif self.tag == "camera" and self.key is not None and not self.graphic_mode: + elif self.kind == "camera" and self.key[2] is not None and not self.graphic_mode: for c in all_keyframes[self.scene].get(self.key, []): _, t, _ = c child = KeyFrame(key_child, t, key_hovere_child, key=self.key, @@ -1691,7 +1755,7 @@ init 1 python in _viewers: generate_menu(key=self.key, check_point=c, change_func=self.changed, opened=self.opened, in_graphic_mode=self.in_graphic_mode), style_prefix="_viewers_alternate_menu")) new_children.append(child) - elif self.tag == "camera" and self.key is not None and self.graphic_mode: + elif self.kind == "camera" and self.key[2] is not None and self.graphic_mode: last_v, last_t = None, None for c in all_keyframes[self.scene].get(self.key, []): v, t, w = c @@ -1719,23 +1783,23 @@ init 1 python in _viewers: new_knot_children.append(knot_child) last_v, last_t = v, t - elif isinstance(self.tag, tuple) and self.props_set is None and self.key is None: - tag, layer = self.tag + elif self.kind == "image" and self.props_set is None and self.key[2] is None: + tag, layer, _ = self.key _all_keyframes = all_keyframes[self.scene] for p in get_image_state(layer, self.scene)[tag]: for _, t, _ in _all_keyframes.get((tag, layer, p), []): child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=None, clicked=Function(change_time, t)) new_children.append(child) - elif isinstance(self.tag, tuple) and self.props_set is not None: - tag, layer = self.tag + elif self.kind == "image" and self.props_set is not None: + tag, layer, _ = self.key _all_keyframes = all_keyframes[self.scene] for p in self.props_set: for _, t, _ in _all_keyframes.get((tag, layer, p), []): child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=None, clicked=Function(change_time, t)) new_children.append(child) - elif isinstance(self.tag, tuple) and self.key is not None and not self.graphic_mode: + elif self.kind == "image" and self.key[2] is not None and not self.graphic_mode: for c in all_keyframes[self.scene].get(self.key, []): _, t, _ = c child = KeyFrame(key_child, t, key_hovere_child, key=self.key, @@ -1744,7 +1808,7 @@ init 1 python in _viewers: generate_menu(key=self.key, check_point=c, change_func=self.changed, opened=self.opened, in_graphic_mode=self.in_graphic_mode), style_prefix="_viewers_alternate_menu")) new_children.append(child) - elif isinstance(self.tag, tuple) and self.key is not None and self.graphic_mode: + elif self.kind == "image" and self.key[2] is not None and self.graphic_mode: last_v, last_t = None, None for c in all_keyframes[self.scene].get(self.key, []): v, t, w = c @@ -1772,13 +1836,13 @@ init 1 python in _viewers: new_knot_children.append(knot_child) last_v, last_t = v, t - elif self.tag == "sounds" and self.key is None: + elif self.kind == "sounds" and self.key is None: for channel, play_times in sound_keyframes.items(): for t in play_times: child = KeyFrame(insensitive_key_child, t, insensitive_key_hovere_child, False, key=channel, clicked=Function(change_time, t)) new_children.append(child) - elif self.tag == "sounds" and self.key is not None: + elif self.kind == "sounds" and self.key is not None: for t in sound_keyframes[self.key]: child = KeyFrame(key_child, t, key_hovere_child, key=self.key, is_sound=True, clicked=Function(change_time, t), @@ -1903,10 +1967,7 @@ init 1 python in _viewers: else: self.yoffset = 0 if self.key is not None: - if isinstance(self.key, tuple): - p = self.key[2] - else: - p = self.key + p = self.key[2] self.force_plus = is_force_plus(p) @@ -2047,19 +2108,12 @@ init 1 python in _viewers: self.yoffset = self.height/2. self.key_list = [key] - if isinstance(key, tuple): - n, l, p = key - check_result = check_props_group(p, (n, l)) - if check_result is not None: - _, ps = check_result + n, l, p = key + check_result = check_props_group(key) + if check_result is not None: + gn, ps = check_result + if n is not None or gn != "focusing": self.key_list = [(n, l, p) for p in ps] - self.force_plus = is_force_plus(p) - else: - check_result = check_props_group(key, "camera") - if check_result is not None: - gn, ps = check_result - if gn != "focusing": - self.key_list = ps self.force_plus = is_force_plus(key) if is_wide_range(key): @@ -2123,8 +2177,8 @@ init 1 python in _viewers: def warperkey_drag_changed(self, y): - bottom_pos = value_to_pos(self.last_v, self.range, self.force_plus) - top_pos = value_to_pos(self.v, self.range, self.force_plus) + bottom_pos = value_to_pos(self.last_v, self.range, self.force_plus, self.key) + top_pos = value_to_pos(self.v, self.range, self.force_plus, self.key) if top_pos < bottom_pos: top_pos, bottom_pos = bottom_pos, top_pos if y >= top_pos: @@ -2175,11 +2229,8 @@ init 1 python in _viewers: self.yoffset = self.height/2. # self.key_list = [key] - if isinstance(key, tuple): - n, l, p = key - self.force_plus = is_force_plus(p) - else: - self.force_plus = is_force_plus(key) + n, l, p = key + self.force_plus = is_force_plus(p) self.int_type = isinstance(get_value(key, default=True), int) if is_wide_range(key): @@ -2218,11 +2269,11 @@ init 1 python in _viewers: def knot_num_to_pos(self): knots = splines[self.scene][self.key][self.key_time] knot = knots[self.knot_num] - return value_to_pos(knot, self.range, self.force_plus) + return value_to_pos(knot, self.range, self.force_plus, self.key) def knot_drag_changed(self, y): - v = pos_to_value(y, is_wide_range(self.key), self.force_plus) + v = pos_to_value(y, is_wide_range(self.key), self.force_plus, self.key) if self.int_type: v = int(v) else: @@ -2276,21 +2327,14 @@ init 1 python in _viewers: self.in_graphic_mode = in_graphic_mode self.key_list = [key] - if key is not None: - if isinstance(key, tuple): - n, l, p = key - check_result = check_props_group(p, (n, l)) - if check_result is not None: - _, ps = check_result + if key is not None and isinstance(key, tuple) and key[2] is not None: + n, l, p = key + check_result = check_props_group(key) + self.force_plus = is_force_plus(key) + if check_result is not None: + gn, ps = check_result + if n is not None or gn != "focusing": self.key_list = [(n, l, p) for p in ps] - self.force_plus = is_force_plus(p) - else: - check_result = check_props_group(key, "camera") - if check_result is not None: - gn, ps = check_result - if gn != "focusing": - self.key_list = ps - self.force_plus = is_force_plus(key) if in_graphic_mode: self.width = config.screen_width-c_box_size-50-key_half_xsize @@ -2352,7 +2396,7 @@ init 1 python in _viewers: if self.in_graphic_mode: time = pos_to_time(x) use_wide_range = is_wide_range(self.key) - value = pos_to_value(y, use_wide_range, self.force_plus) + value = pos_to_value(y, use_wide_range, self.force_plus, self.key) generate_changed(self.key)(to_changed_value(value, self.force_plus, use_wide_range), time) else: time = pos_to_time(x) @@ -2365,10 +2409,11 @@ init 1 python in _viewers: class ImagePins(renpy.Displayable): - def __init__(self): + def __init__(self, layer): super(ImagePins, self).__init__() from pygame import MOUSEMOTION from renpy.store import Fixed + self.layer = layer if aspect_16_9: self.xpos = int(config.screen_width * (1 - preview_size) / 2) self.ypos = 0 @@ -2386,6 +2431,8 @@ init 1 python in _viewers: def __eq__(self, other): if not isinstance(other, ImagePins): return False + if other.layer != self.layer: + return False return True @@ -2393,22 +2440,21 @@ init 1 python in _viewers: new_children = [] box = Fixed() - for l in ["master"]: - state = get_image_state(l) - for tag in state: - ks = self.get_image_keyframes(tag, l) - if not ks or (current_time not in ks and ks[-1] > current_time): - child = ImagePin(tag, l, current_scene, current_time) - new_children.append(child) - last_t = None - for t in ks: - child = ImagePin(tag, l, current_scene, t) - new_children.append(child) - if last_t is not None: - t_diff = (t - last_t) - for i in range(1, self.mark_num): - box.add(ImageInterpolate(tag, l, current_scene, (i * (t - last_t) / self.mark_num)+last_t).get_child()) - last_t = t + state = get_image_state(self.layer) + for tag in state: + ks = self.get_image_keyframes(tag, self.layer) + if not ks or (current_time not in ks and ks[-1] > current_time): + child = ImagePin(tag, self.layer, current_scene, current_time) + new_children.append(child) + last_t = None + for t in ks: + child = ImagePin(tag, self.layer, current_scene, t) + new_children.append(child) + if last_t is not None: + t_diff = (t - last_t) + for i in range(1, self.mark_num): + box.add(ImageInterpolate(tag, self.layer, current_scene, (i * (t - last_t) / self.mark_num)+last_t).get_child()) + last_t = t children = [] for new_c in new_children: @@ -2424,17 +2470,17 @@ init 1 python in _viewers: self.children = children new_children = [] - ks = self.get_camera_keyframes() + ks = self.get_camera_keyframes(self.layer) if not ks or (current_time not in ks and ks[-1] > current_time): - child = CameraPin(current_scene, current_time) + child = CameraPin(self.layer, current_scene, current_time) new_children.append(child) last_t = None for t in ks: - child = CameraPin(current_scene, t) + child = CameraPin(self.layer, current_scene, t) new_children.append(child) if last_t is not None: for i in range(1, self.mark_num): - box.add(CameraInterpolate(current_scene, (i * (t - last_t) / self.mark_num) + last_t).get_child()) + box.add(CameraInterpolate(self.layer, current_scene, (i * (t - last_t) / self.mark_num) + last_t).get_child()) last_t = t camera_children = [] @@ -2487,13 +2533,13 @@ init 1 python in _viewers: return sorted(list(result)) - def get_camera_keyframes(self): + def get_camera_keyframes(self, layer): result = set() for p in ["xpos", "ypos", "zpos"]: - if p not in all_keyframes[current_scene]: + if (None, layer, p) not in all_keyframes[current_scene]: continue else: - for _, t, _ in all_keyframes[current_scene][p]: + for _, t, _ in all_keyframes[current_scene][(None, layer, p)]: result.add(t) return sorted(tuple(result)) @@ -2521,13 +2567,11 @@ init 1 python in _viewers: def value_to_pos(self): xpos = get_value((self.tag, self.layer, "xpos"), self.time, True, self.scene_num) - if isinstance(xpos, float): - xpos *= config.screen_width + xpos = get_absolute_from_pos(xpos, config.screen_width) xpos *= preview_size ypos = get_value((self.tag, self.layer, "ypos"), self.time, True, self.scene_num) - if isinstance(ypos, float): - ypos *= config.screen_height + ypos = get_absolute_from_pos(ypos, config.screen_height) ypos *= preview_size return (xpos, ypos) @@ -2547,13 +2591,13 @@ init 1 python in _viewers: from pygame import MOUSEMOTION, KMOD_CTRL, KMOD_SHIFT, KMOD_ALT from pygame.key import get_mods from pygame.mouse import get_pressed - from renpy.store import Show, QueueEvent, Function, NullAction + from renpy.store import Show, SensitiveIf, QueueEvent, Function, NullAction self.tag = tag self.layer = layer self.scene_num = scene_num self.time = time - self.clicked = [Show("_new_action_editor", opened={scene_num:[tag, (tag, layer, "Child/Pos ")]}), Function(change_time, time)] + [QueueEvent("mouseup_1")] + self.clicked = [Show("_new_action_editor", opened={scene_num:[(tag, layer, None), (tag, layer, "Child/Pos ")]}), Function(change_time, time)] + [QueueEvent("mouseup_1")] self.draggable = scene_num == current_scene if self.draggable: @@ -2574,7 +2618,7 @@ init 1 python in _viewers: self.z_changed = generate_changed((self.tag, self.layer, "zpos")) self.r_changed = generate_changed((self.tag, self.layer, "rotate")) self.alternate = ShowAlternateMenu( - [("{}".format(tag), NullAction()), + [("{}".format(tag), Show("_new_action_editor", opened={scene_num:[(tag, layer, None), (tag, layer, "Child/Pos ")]})), ("edit: xpos {}".format(xpos), [Function(edit_value, self.x_changed, xpos, is_wide_range((tag, layer, "xpos")), is_force_plus("xpos"), time), Function(change_time, time)]), ("edit: ypos {}".format(ypos), @@ -2582,7 +2626,9 @@ init 1 python in _viewers: ("edit: zpos {}".format(zpos), [Function(edit_value, self.z_changed, zpos, is_wide_range((tag, layer, "zpos")), is_force_plus("zpos"), time), Function(change_time, time)]), ("edit: rotate {}".format(rotate), - [Function(edit_value, self.r_changed, rotate, is_wide_range((tag, layer, "rotate")), is_force_plus("rotate"), time), Function(change_time, time)])], + [Function(edit_value, self.r_changed, rotate, is_wide_range((tag, layer, "rotate")), is_force_plus("rotate"), time), Function(change_time, time)]), + ("remove", + [SensitiveIf(tag in image_state[self.scene_num][layer]), Show("_new_action_editor", layer=layer), Function(remove_image, layer, tag)])], style_prefix="_viewers_alternate_menu") self.dragging = False @@ -2691,17 +2737,23 @@ init 1 python in _viewers: x /= preview_size if isinstance(xpos_org, int): x = int(x) - else: + elif isinstance(xpos_org, float): x /= config.screen_width + elif check_new_position_type(xpos_org): + x = renpy.atl.position.from_any(int(x)) + # x = int(x) - ypos_org = state["xpos"] + ypos_org = state["ypos"] if ypos_org is None: - ypos_org = get_default("xpos") + ypos_org = get_default("ypos") y /= preview_size if isinstance(ypos_org, int): y = int(y) - else: + elif isinstance(ypos_org, float): y /= config.screen_height + elif check_new_position_type(ypos_org): + y = renpy.atl.position.from_any(int(y)) + # y = int(y) mods = self.get_mods() @@ -2776,7 +2828,8 @@ init 1 python in _viewers: class CameraInterpolate(): - def __init__(self, scene_num, time): + def __init__(self, layer, scene_num, time): + self.layer = layer self.scene_num = scene_num self.time = time @@ -2794,15 +2847,13 @@ init 1 python in _viewers: def value_to_pos(self): - xpos = get_value("xpos", self.time, True, self.scene_num) - if isinstance(xpos, float): - xpos *= config.screen_width + xpos = get_value((None, self.layer, "xpos"), self.time, True, self.scene_num) + xpos = get_absolute_from_pos(xpos, config.screen_width) xpos *= preview_size xpos += self.xoffset - ypos = get_value("ypos", self.time, True, self.scene_num) - if isinstance(ypos, float): - ypos *= config.screen_height + ypos = get_value((None, self.layer, "ypos"), self.time, True, self.scene_num) + ypos = get_absolute_from_pos(ypos, config.screen_height) ypos *= preview_size ypos += self.yoffset @@ -2818,44 +2869,45 @@ init 1 python in _viewers: class CameraPin(CameraInterpolate): - def __init__(self, scene_num, time): - super(CameraPin, self).__init__(scene_num, time) + def __init__(self, layer, scene_num, time): + super(CameraPin, self).__init__(layer, scene_num, time) from pygame import MOUSEMOTION, KMOD_CTRL, KMOD_SHIFT, KMOD_ALT from pygame.key import get_mods from pygame.mouse import get_pressed from renpy.store import Show, QueueEvent, Function, NullAction self.scene_num = scene_num self.time = time + self.layer = layer - self.clicked = [Show("_new_action_editor", opened={scene_num:["camera", "Child/Pos "]}), Function(change_time, time)] + [QueueEvent("mouseup_1")] + self.clicked = [Show("_new_action_editor", opened={scene_num:[(None, layer, None), "Child/Pos "]}), Function(change_time, time)] + [QueueEvent("mouseup_1")] self.draggable = scene_num == current_scene if self.draggable: - self.x_changed = generate_changed("xpos") - self.y_changed = generate_changed("ypos") - self.z_changed = generate_changed("zpos") - self.r_changed = generate_changed("rotate") - xpos = get_value("xpos", time, True, self.scene_num) + self.x_changed = generate_changed((None, self.layer, "xpos")) + self.y_changed = generate_changed((None, self.layer, "ypos")) + self.z_changed = generate_changed((None, self.layer, "zpos")) + self.r_changed = generate_changed((None, self.layer, "rotate")) + xpos = get_value((None, self.layer, "xpos"), time, True, self.scene_num) if isinstance(xpos, float): xpos = round(xpos, 2) - ypos = get_value("ypos", time, True, self.scene_num) + ypos = get_value((None, self.layer, "ypos"), time, True, self.scene_num) if isinstance(ypos, float): ypos = round(ypos, 2) - zpos = get_value("zpos", time, True, self.scene_num) + zpos = get_value((None, self.layer, "zpos"), time, True, self.scene_num) if isinstance(zpos, float): zpos = round(zpos, 2) - rotate = get_value("rotate", time, True, self.scene_num) + rotate = get_value((None, self.layer, "rotate"), time, True, self.scene_num) if isinstance(rotate, float): rotate = round(rotate, 2) self.alternate = ShowAlternateMenu([ ("camera", NullAction()), ("edit: xpos {}".format(xpos), - [Function(edit_value, self.x_changed, xpos, is_wide_range("xpos"), is_force_plus("xpos"), time), Function(change_time, time)]), + [Function(edit_value, self.x_changed, xpos, is_wide_range((None, self.layer, "xpos")), is_force_plus("xpos"), time), Function(change_time, time)]), ("edit: ypos {}".format(ypos), - [Function(edit_value, self.y_changed, ypos, is_wide_range("ypos"), is_force_plus("ypos"), time), Function(change_time, time)]), + [Function(edit_value, self.y_changed, ypos, is_wide_range((None, self.layer, "ypos")), is_force_plus("ypos"), time), Function(change_time, time)]), ("edit: zpos {}".format(zpos), - [Function(edit_value, self.z_changed, zpos, is_wide_range("zpos"), is_force_plus("zpos"), time), Function(change_time, time)]), + [Function(edit_value, self.z_changed, zpos, is_wide_range((None, self.layer, "zpos")), is_force_plus("zpos"), time), Function(change_time, time)]), ("edit: rotate {}".format(rotate), - [Function(edit_value, self.r_changed, rotate, is_wide_range("rotate"), is_force_plus("rotate"), time), Function(change_time, time)])], + [Function(edit_value, self.r_changed, rotate, is_wide_range((None, self.layer, "rotate")), is_force_plus("rotate"), time), Function(change_time, time)])], style_prefix="_viewers_alternate_menu") self.dragging = False @@ -2897,6 +2949,8 @@ init 1 python in _viewers: def __eq__(self, other): + if self.layer != other.layer: + return False if self.scene_num != other.scene_num: return False if self.time != other.time: @@ -2933,7 +2987,7 @@ init 1 python in _viewers: def pos_to_value(self, x, y): - state = camera_state_org[self.scene_num] + state = camera_state_org[self.scene_num][self.layer] r = sqrt((x - self.last_x)**2 + (y - self.last_y)**2) if x - self.last_x >= 0: r *= -1 @@ -2961,16 +3015,16 @@ init 1 python in _viewers: mods = self.get_mods() if mods & self.KMOD_SHIFT and mods & self.KMOD_CTRL: - self.r_changed(to_changed_value(r+self.last_rotate, is_force_plus("rotate"), is_wide_range("rotate"))) + self.r_changed(to_changed_value(r+self.last_rotate, is_force_plus("rotate"), is_wide_range((None, self.layer, "rotate")))) elif mods & self.KMOD_CTRL: - self.y_changed(to_changed_value(y, is_force_plus("ypos"), is_wide_range("ypos"))) + self.y_changed(to_changed_value(y, is_force_plus("ypos"), is_wide_range((None, self.layer, "ypos")))) elif mods & self.KMOD_SHIFT: - self.x_changed(to_changed_value(x, is_force_plus("xpos"), is_wide_range("xpos"))) + self.x_changed(to_changed_value(x, is_force_plus("xpos"), is_wide_range((None, self.layer, "xpos")))) elif mods & self.KMOD_ALT: - self.z_changed(to_changed_value(r+self.last_zpos, is_force_plus("zpos"), is_wide_range("zpos"))) + self.z_changed(to_changed_value(r+self.last_zpos, is_force_plus("zpos"), is_wide_range((None, self.layer, "zpos")))) else: - self.x_changed(to_changed_value(x, is_force_plus("xpos"), is_wide_range("xpos"))) - self.y_changed(to_changed_value(y, is_force_plus("ypos"), is_wide_range("ypos"))) + self.x_changed(to_changed_value(x, is_force_plus("xpos"), is_wide_range((None, self.layer, "xpos")))) + self.y_changed(to_changed_value(y, is_force_plus("ypos"), is_wide_range((None, self.layer, "ypos")))) def event(self, ev, x, y, st): @@ -2998,8 +3052,8 @@ init 1 python in _viewers: self.clicking = True self.last_x = x self.last_y = y - self.last_zpos = get_value("zpos", default=True, scene_num=self.scene_num) - self.last_rotate = get_value("rotate", default=True, scene_num=self.scene_num) + self.last_zpos = get_value((None, self.layer, "zpos"), default=True, scene_num=self.scene_num) + self.last_rotate = get_value((None, self.layer, "rotate"), default=True, scene_num=self.scene_num) raise renpy.display.core.IgnoreEvent() elif not self.dragging and renpy.map_event(ev, "mouseup_1"): if self.clicking == True: @@ -3046,31 +3100,17 @@ init 1 python in _viewers: check_points = all_keyframes[current_scene][key] i = check_points.index(check_point) (v, t, w) = check_point - if isinstance(key, tuple): - n, l, p = key - mkey = (n, l) - k_list = [key] - check_points_list = [check_points] - loop_button_action = [SelectedIf(loops[current_scene][key]), ToggleDict(loops[current_scene], key)] - check_result = check_props_group(p, mkey) - if check_result is not None: - gn, ps = check_result + n, l, p = key + k_list = [key] + check_points_list = [check_points] + loop_button_action = [SelectedIf(loops[current_scene][key]), ToggleDict(loops[current_scene], key)] + check_result = check_props_group(key) + if check_result is not None: + gn, ps = check_result + if n is not None or gn != "focusing": k_list = [(n, l, p) for p in ps] check_points_list = [all_keyframes[current_scene][k2] for k2 in k_list] loop_button_action = [SelectedIf(loops[current_scene][(k_list+[(n, l, gn)])[0]])] + [ToggleDict(loops[current_scene], k2) for k2 in k_list+[(n, l, gn)]] - else: - k_list = [key] - mkey = "camera" - p = key - check_points_list = [check_points] - loop_button_action = [SelectedIf(loops[current_scene][key]), ToggleDict(loops[current_scene], key)] - check_result = check_props_group(p, mkey) - if check_result is not None: - gn, ps = check_result - if gn != "focusing": - k_list = ps - check_points_list = [all_keyframes[current_scene][k2] for k2 in k_list] - loop_button_action = [SelectedIf(loops[current_scene][(k_list+[gn])[0]])] + [ToggleDict(loops[current_scene], k2) for k2 in k_list+[gn]] button_list = [] @@ -3095,7 +3135,7 @@ init 1 python in _viewers: if i > 0 and in_graphic_mode: button_list.append(( _("use warper generator"), [SelectedIf(w.startswith("warper_generator")), Function(use_warper_generator, check_points=check_points_list, old=t)])) - check_result = check_props_group(p, mkey) + check_result = check_props_group(key) if (check_result is None and p not in disallow_spline) or (check_result[0] not in disallow_spline): if i > 0: button_list.append(( _("spline editor"), diff --git a/game/dev/actioneditor/README.md b/game/dev/actioneditor/README.md index 02b67d3..87a58a9 100644 --- a/game/dev/actioneditor/README.md +++ b/game/dev/actioneditor/README.md @@ -2,8 +2,8 @@ 日本語マニュアルはドキュメント後半にあります。 This script adds Ren'py the ability to adjust and view transform properties of images - and camera by in-game Action Editor and Image Viewer and Sound Viewer. - Many warpers and usefull functions intended to be used in function statement in ATL are + and camera by in-game Action Editor, Image Viewer and Sound Viewer. + Many warpers and useful functions intended to be used in function statements in ATL are also added. Ren'Py @@ -26,32 +26,32 @@ lemma forum ================ This script adds Ren'py the ability to adjust and view transform properties of images - and camera by in-game Action Editor and Image Viewer and Sound Viewer. - Many warpers and usefull functions intended to be used in function statement in ATL are + and camera by in-game Action Editor, Image Viewer and Sound Viewer. + Many warpers and useful functions intended to be used in function statements in ATL are also added. About old version ================ This is available in v7.4.5 later. - To use in older version, use old version ActionEditor. + To use an older version, use the old version of ActionEditor. - - In current version ActionEditor, below functions are removed. + + In the current version of ActionEditor, the below functions are removed. * expression * loading last action - To instal + To install ================ To install, copy all files in the camera directory into your game directory. ActionEditor.rpy is required for release version if you use camera blur or warper_generator. 00warper.rpy is also required if you use added warpers. ATL_funcctions.rpy is also required if you use added functions for ATL statement. - Ohter files arenot required. + Other files are not required. Action Editor ================ - This allows you to adjusts transform properties of camera and images in + This allows you to adjust transform properties of camera and images in real-time with a GUI. It can then generate a script based on these changes and place it on the clipboard for later pasting into Ren'Py scripts. @@ -60,32 +60,32 @@ lemma forum The Action Editor has the following features: - * View and adjust the transform properties of images and camera with adjusting a bar or typing value. + * View and adjust the transform properties of images and camera by adjusting a bar or typing a value. * View and adjust the x and y coordinates of the camera with a draggable camera icon. * Adjust the z coordinate of the camera with the mouse wheel. * Adjust the x,y coordinate of the camera with the keyboard(hjkl, HJKL, wasd, WASD). - * Reset the value with right-click on the value button. + * Reset the value by right-clicking on the value button. * Add, delete, and edit keyframes on a timeline like video editing software. - * the spline motion and loop is availabe + * The spline motion and loop is available * After setting up a scene with the desired look and animations, the Action Editor will generate a script and place it on your clipboard for pasting into your Ren'Py scripts. (v6.99 and later only) * Introducing the concept of depth of field and allow to adjust focus position and dof. - Blur each image according to dof, focus postion and the distance between the camera and the image. + Blur each image according to dof, focus position and the distance between the camera and the image. * Show, replace and hide a image with transition(use None for a image name to hide image) * Change a scene with scene statement. * There is the option for hiding window during the ATL animation in clipboard data. * There is the option for allowing to skip ATL animation in clipboard data. Note - * blur transform property of each images are used for simulating camera blur in function transform prperty, - so blur transform properties of each images aren't availabe when focusing is enabled. - Set function property to None when you want to disable camera blur for already shownd images. - * Unfortunately, The behavior of functions for function property isn't same as ATL. + * blur transform property of each images are used for simulating camera blur in function transform property, + so blur transform properties of each images aren't available when focusing is enabled. + Set function property to None when you want to disable camera blur for already shown images. + * Unfortunately, The behavior of functions for function property isn't the same as ATL. There are some different points. 1. inherited_ have no value. - 2. Setting properties have no affect when it is called next time. - 3. The return value from it have no affect. + 2. Setting properties have no effect when it is called next time. + 3. The return value from it has no effect. 4. It isn't always called in time. ActionEditor can't get current function when opened. @@ -102,8 +102,8 @@ lemma forum Commonly, add the following variables the property name you want to add to be added.: - * `props_set`: control where that is shwon in ActionEditor. - * `sort_order_list`: control where that is shwon in clipboard. + * `props_set`: control where that is shown in ActionEditor. + * `sort_order_list`: control where that is shown in the clipboard. * `transform_props` or `camera_props`: Add the the property name. Adding `transform_props` shows it in each images and Adding `camera_props` shows it in camera. * `property_default_value`: Add the default value of the property. @@ -137,7 +137,7 @@ lemma forum any_props = {"blend"} menu_props = {"blend":[None] + [key for key in config.gl_blend_func]} - Exclusive proparties like tile and pan should be set in `exclusive`. example: + Exclusive properties like tile and pan should be set in `exclusive`. example: exclusive = ( ({"xpos", "ypos"}, {"xalignaround", "yalignaround", "radius", "angle"}), @@ -148,7 +148,7 @@ lemma forum Property Group ================ - For tuple, You can use `props_groups` where that key is the property name and that value + For tuples, you can use `props_groups` where that key is the property name and that value is the tuple of each element name, so that they can be edited individually. example: props_groups = { @@ -164,18 +164,18 @@ lemma forum If `config.developer` is True, pressing Shift+U or +(add image) textbutton on ActionEditor to open Image Viewer. - Defined image tag and attribute textbuttons are shown in this viewer. + Defined image tags and attribute textbuttons are shown in this viewer. You can filter them by the text entered in the top-most text entry field. - The completion feature is also availabe by tab. + The completion feature is also available by tab. - When these textbuttons get focus and the same image name exist as these text, the image is + When these textbuttons get focus and the same image name exists as these text, the image is shown. Pressing the textbutton add that text to the filter if the same image name doesn't exist as that text. the image is added to ActionEditor if that exist and viewer is opened by ActionEditor. the image name is outputted to the clipboard if that exist and viewer isn't opened by ActionEditor. - Pressing clipboard buton at the bottom also outputs the filter string to clipboard + Pressing clipboard button at the bottom also outputs the filter string to clipboard Sound Viewer @@ -186,17 +186,17 @@ lemma forum Variable names in audio store are shown in this viewer. The music files should be automatically defined as these name. You can filter them by the text entered in the top-most text entry field. - The completion feature is also availabe by tab. + The completion feature is also available by tab. When these textbuttons get focus and the music file is played. - Pressing the textbutton add that to ActionEditor if the viewer is opened by ActionEditor. + Pressing the textbutton adds that to ActionEditor if the viewer is opened by ActionEditor. Otherwise, that name is outputted to the clipboard. - Pressing clipboard buton at the bottom also outputs the filter string to clipboard + Pressing clipboard button at the bottom also outputs the filter string to clipboard ATL functions ================ - ATL_funcctions.rpy adds usefull functions which are intended to be used for + ATL_funcctions.rpy adds useful functions which are intended to be used for function statement in ATL block. For more information, see that file. @@ -235,23 +235,28 @@ lemma forum style new_action_editor_text: size 10 - Trouble shooting + Troubleshooting ================ Layout is corrupted - Too long tag names and big size of fonts corrupt the layout of ActionEditor. + Too long tag names and big font sizes corrupt the layout of ActionEditor. In that case, try to adjust the size of font by the above manner. Known issue ================ - ActionEditor can't show camera and displayable with "at clause" includeing animation correctly. + ActionEditor can't show camera and displayable with "at clause" including animation correctly. ActionEditor can't show movie and animation displayable correctly. + Note +================ + + The clipboard from ActionEditor includes the difference from the state when ActionEditor is opened on. Therefore that is intended to be pasted on next that line. Images aren't shown correctly if the existing lines are overwritten. + 日本語ドキュメント ================ @@ -497,3 +502,8 @@ lemma forum アニメーションするat 節を使用したcamera, displayableは正常に表示できない Movie Displayable, アニメーションするDisplayableは正常に表示できない + + 注意 +================ + + ActionEditorから出力されるクリップボードデータはActionEditorが開かれた状態からの差分として出力され、その次の行への貼り付けを意図しています。このため既存のshowステートメントを上書きするように貼り付けると正常に表示されない場合があります。 diff --git a/game/dev/actioneditor/image_viewer.rpy b/game/dev/actioneditor/image_viewer.rpy index 52e0a24..0a37779 100644 --- a/game/dev/actioneditor/image_viewer.rpy +++ b/game/dev/actioneditor/image_viewer.rpy @@ -11,6 +11,7 @@ screen _image_selecter(default=""): style_group "image_selecter" vbox: label _("Type a image name") style "image_selecter_input" + label _("Tab: completion") style "image_selecter_input" input value ScreenVariableInputValue("filter_string", default=True, returnable=True) copypaste True style "image_selecter_input" id "input_filter_strings" $filtered_list = _viewers.filter_image_name(filter_string) viewport: @@ -101,10 +102,22 @@ init -2000 python in _viewers: for e in es.split()[1:]: if e.startswith(completed_string): candidate.append(e) - cs = renpy.current_screen() - cs.scope["filter_string"] += candidate[0][len(completed_string):] + " " - input = renpy.get_displayable("_image_selecter", "input_filter_strings") - input.caret_pos = len(cs.scope["filter_string"]) + + if candidate: + cs = renpy.current_screen() + if len(candidate) > 1: + completed_candidate = candidate[0] + for c in candidate[1:]: + for i in range(len(completed_candidate)): + if i < len(c) and completed_candidate[i] != c[i]: + completed_candidate = completed_candidate[0:i] + break + cs.scope["filter_string"] += completed_candidate[len(completed_string):] + else: + completed_candidate = candidate[0] + cs.scope["filter_string"] += completed_candidate[len(completed_string):] + " " + input = renpy.get_displayable("_image_selecter", "input_filter_strings") + input.caret_pos = len(cs.scope["filter_string"]) def _image_viewer_hide(): renpy.hide("preview", layer="screens") diff --git a/game/dev/actioneditor/sound_viewer.rpy b/game/dev/actioneditor/sound_viewer.rpy index b93f177..6a430aa 100644 --- a/game/dev/actioneditor/sound_viewer.rpy +++ b/game/dev/actioneditor/sound_viewer.rpy @@ -11,6 +11,7 @@ screen _sound_selector(default=""): style_group "sound_selecter" vbox: label _("type filenames(ex: variable, '' or [[variable, variable])") style "sound_selecter_input" + label _("Tab: completion") style "sound_selecter_input" input value ScreenVariableInputValue("filter_string", default=True, returnable=True) copypaste True style "sound_selecter_input" id "input_filter_strings" $filtered_list = _viewers.filter_sound_name(filter_string) viewport: @@ -95,8 +96,17 @@ init -2000 python in _viewers: if isinstance(file, str) and renpy.loadable(file): candidate.append(name) if candidate: + if len(candidate) > 1: + completed_candidate = candidate[0] + for c in candidate[1:]: + for i in range(len(completed_candidate)): + if i < len(c) and completed_candidate[i] != c[i]: + completed_candidate = completed_candidate[0:i] + break + else: + completed_candidate = candidate[0] cs = renpy.current_screen() - cs.scope["filter_string"] += candidate[0][len(last_element):] + cs.scope["filter_string"] += completed_candidate[len(last_element):] input = renpy.get_displayable("_sound_selector", "input_filter_strings") input.caret_pos = len(cs.scope["filter_string"]) diff --git a/game/dev/metro/bg_metro empty.png b/game/dev/metro/bg_metro empty.png new file mode 100644 index 0000000..68b8e98 Binary files /dev/null and b/game/dev/metro/bg_metro empty.png differ diff --git a/game/dev/metro/bg_metro olivia.png b/game/dev/metro/bg_metro olivia.png new file mode 100644 index 0000000..7107f8a Binary files /dev/null and b/game/dev/metro/bg_metro olivia.png differ diff --git a/game/dev/metro/bg_metro passengers.png b/game/dev/metro/bg_metro passengers.png new file mode 100644 index 0000000..1468b9a Binary files /dev/null and b/game/dev/metro/bg_metro passengers.png differ diff --git a/game/dev/old/ben/ben angry.png b/game/dev/old/ben/ben angry.png new file mode 100644 index 0000000..352b3fb Binary files /dev/null and b/game/dev/old/ben/ben angry.png differ diff --git a/game/dev/old/ben/ben considering.png b/game/dev/old/ben/ben considering.png new file mode 100644 index 0000000..5f2f20c Binary files /dev/null and b/game/dev/old/ben/ben considering.png differ diff --git a/game/dev/old/ben/ben explanatory.png b/game/dev/old/ben/ben explanatory.png new file mode 100644 index 0000000..4ef9fdb Binary files /dev/null and b/game/dev/old/ben/ben explanatory.png differ diff --git a/game/dev/old/ben/ben happy.png b/game/dev/old/ben/ben happy.png new file mode 100644 index 0000000..a96613b Binary files /dev/null and b/game/dev/old/ben/ben happy.png differ diff --git a/game/dev/old/ben/ben neutral.png b/game/dev/old/ben/ben neutral.png new file mode 100644 index 0000000..c11124d Binary files /dev/null and b/game/dev/old/ben/ben neutral.png differ diff --git a/game/dev/old/ben/ben sad.png b/game/dev/old/ben/ben sad.png new file mode 100644 index 0000000..5514ca0 Binary files /dev/null and b/game/dev/old/ben/ben sad.png differ diff --git a/game/dev/old/ben/ben shock.png b/game/dev/old/ben/ben shock.png new file mode 100644 index 0000000..c20d9c7 Binary files /dev/null and b/game/dev/old/ben/ben shock.png differ diff --git a/game/dev/old/ben/ben unimpressed.png b/game/dev/old/ben/ben unimpressed.png new file mode 100644 index 0000000..f5f39ee Binary files /dev/null and b/game/dev/old/ben/ben unimpressed.png differ diff --git a/game/dev/old/damien/damien angry.png b/game/dev/old/damien/damien angry.png new file mode 100644 index 0000000..01b6b6e Binary files /dev/null and b/game/dev/old/damien/damien angry.png differ diff --git a/game/dev/old/damien/damien considering.png b/game/dev/old/damien/damien considering.png new file mode 100644 index 0000000..b04078c Binary files /dev/null and b/game/dev/old/damien/damien considering.png differ diff --git a/game/dev/old/damien/damien explanatory.png b/game/dev/old/damien/damien explanatory.png new file mode 100644 index 0000000..67483d6 Binary files /dev/null and b/game/dev/old/damien/damien explanatory.png differ diff --git a/game/dev/old/damien/damien happy.png b/game/dev/old/damien/damien happy.png new file mode 100644 index 0000000..b3962e9 Binary files /dev/null and b/game/dev/old/damien/damien happy.png differ diff --git a/game/dev/old/damien/damien neutral.png b/game/dev/old/damien/damien neutral.png new file mode 100644 index 0000000..c771045 Binary files /dev/null and b/game/dev/old/damien/damien neutral.png differ diff --git a/game/dev/old/damien/damien sad.png b/game/dev/old/damien/damien sad.png new file mode 100644 index 0000000..8112063 Binary files /dev/null and b/game/dev/old/damien/damien sad.png differ diff --git a/game/dev/old/damien/damien shock.png b/game/dev/old/damien/damien shock.png new file mode 100644 index 0000000..8762f06 Binary files /dev/null and b/game/dev/old/damien/damien shock.png differ diff --git a/game/dev/old/damien/damien unimpressed.png b/game/dev/old/damien/damien unimpressed.png new file mode 100644 index 0000000..a45d675 Binary files /dev/null and b/game/dev/old/damien/damien unimpressed.png differ diff --git a/game/dev/old/inco/inco angry.png b/game/dev/old/inco/inco angry.png new file mode 100644 index 0000000..39a295d Binary files /dev/null and b/game/dev/old/inco/inco angry.png differ diff --git a/game/dev/old/inco/inco annoyed.png b/game/dev/old/inco/inco annoyed.png new file mode 100644 index 0000000..cf8054f Binary files /dev/null and b/game/dev/old/inco/inco annoyed.png differ diff --git a/game/dev/old/inco/inco confused.png b/game/dev/old/inco/inco confused.png new file mode 100644 index 0000000..54024fc Binary files /dev/null and b/game/dev/old/inco/inco confused.png differ diff --git a/game/dev/old/inco/inco considering.png b/game/dev/old/inco/inco considering.png new file mode 100644 index 0000000..d1aad89 Binary files /dev/null and b/game/dev/old/inco/inco considering.png differ diff --git a/game/dev/old/inco/inco happy.png b/game/dev/old/inco/inco happy.png new file mode 100644 index 0000000..9000bd1 Binary files /dev/null and b/game/dev/old/inco/inco happy.png differ diff --git a/game/dev/old/inco/inco sad.png b/game/dev/old/inco/inco sad.png new file mode 100644 index 0000000..d3b687c Binary files /dev/null and b/game/dev/old/inco/inco sad.png differ diff --git a/game/dev/old/inco/inco shock.png b/game/dev/old/inco/inco shock.png new file mode 100644 index 0000000..4616a30 Binary files /dev/null and b/game/dev/old/inco/inco shock.png differ diff --git a/game/dev/old/inco/inco smug.png b/game/dev/old/inco/inco smug.png new file mode 100644 index 0000000..c4457fc Binary files /dev/null and b/game/dev/old/inco/inco smug.png differ diff --git a/game/dev/old/inco/inco wtf.png b/game/dev/old/inco/inco wtf.png new file mode 100644 index 0000000..d8e3783 Binary files /dev/null and b/game/dev/old/inco/inco wtf.png differ diff --git a/game/dev/old/liz head/liz head angry.png b/game/dev/old/liz head/liz head angry.png new file mode 100644 index 0000000..e0f0bdc Binary files /dev/null and b/game/dev/old/liz head/liz head angry.png differ diff --git a/game/dev/old/liz head/liz head happy.png b/game/dev/old/liz head/liz head happy.png new file mode 100644 index 0000000..643a471 Binary files /dev/null and b/game/dev/old/liz head/liz head happy.png differ diff --git a/game/dev/old/liz head/liz head sad.png b/game/dev/old/liz head/liz head sad.png new file mode 100644 index 0000000..50aaa20 Binary files /dev/null and b/game/dev/old/liz head/liz head sad.png differ diff --git a/game/dev/old/liz/liz angry.png b/game/dev/old/liz/liz angry.png new file mode 100644 index 0000000..a46c958 Binary files /dev/null and b/game/dev/old/liz/liz angry.png differ diff --git a/game/dev/old/liz/liz considering.png b/game/dev/old/liz/liz considering.png new file mode 100644 index 0000000..c0a61d1 Binary files /dev/null and b/game/dev/old/liz/liz considering.png differ diff --git a/game/dev/old/liz/liz explanatory.png b/game/dev/old/liz/liz explanatory.png new file mode 100644 index 0000000..a101d75 Binary files /dev/null and b/game/dev/old/liz/liz explanatory.png differ diff --git a/game/dev/old/liz/liz happy.png b/game/dev/old/liz/liz happy.png new file mode 100644 index 0000000..0a2a8d5 Binary files /dev/null and b/game/dev/old/liz/liz happy.png differ diff --git a/game/dev/old/liz/liz neutral.png b/game/dev/old/liz/liz neutral.png new file mode 100644 index 0000000..4fe0604 Binary files /dev/null and b/game/dev/old/liz/liz neutral.png differ diff --git a/game/dev/old/liz/liz sad.png b/game/dev/old/liz/liz sad.png new file mode 100644 index 0000000..b5a6533 Binary files /dev/null and b/game/dev/old/liz/liz sad.png differ diff --git a/game/dev/old/liz/liz shock no head.png b/game/dev/old/liz/liz shock no head.png new file mode 100644 index 0000000..5e69b91 Binary files /dev/null and b/game/dev/old/liz/liz shock no head.png differ diff --git a/game/dev/old/liz/liz shock.png b/game/dev/old/liz/liz shock.png new file mode 100644 index 0000000..c879897 Binary files /dev/null and b/game/dev/old/liz/liz shock.png differ diff --git a/game/dev/old/liz/liz unimpressed no head.png b/game/dev/old/liz/liz unimpressed no head.png new file mode 100644 index 0000000..2d7f90f Binary files /dev/null and b/game/dev/old/liz/liz unimpressed no head.png differ diff --git a/game/dev/old/liz/liz unimpressed.png b/game/dev/old/liz/liz unimpressed.png new file mode 100644 index 0000000..a1f24aa Binary files /dev/null and b/game/dev/old/liz/liz unimpressed.png differ diff --git a/game/dev/old/lizhead/lizhead angry.png b/game/dev/old/lizhead/lizhead angry.png new file mode 100644 index 0000000..fa3ff75 Binary files /dev/null and b/game/dev/old/lizhead/lizhead angry.png differ diff --git a/game/dev/old/lizhead/lizhead happy.png b/game/dev/old/lizhead/lizhead happy.png new file mode 100644 index 0000000..3187d07 Binary files /dev/null and b/game/dev/old/lizhead/lizhead happy.png differ diff --git a/game/dev/old/lizhead/lizhead sad.png b/game/dev/old/lizhead/lizhead sad.png new file mode 100644 index 0000000..6c87fbf Binary files /dev/null and b/game/dev/old/lizhead/lizhead sad.png differ diff --git a/game/dev/old/lizhead/lizhead shock.png b/game/dev/old/lizhead/lizhead shock.png new file mode 100644 index 0000000..cea02f5 Binary files /dev/null and b/game/dev/old/lizhead/lizhead shock.png differ diff --git a/game/dev/old/mia/mia angry.png b/game/dev/old/mia/mia angry.png new file mode 100644 index 0000000..ede0108 Binary files /dev/null and b/game/dev/old/mia/mia angry.png differ diff --git a/game/dev/old/mia/mia considering.png b/game/dev/old/mia/mia considering.png new file mode 100644 index 0000000..30d20ec Binary files /dev/null and b/game/dev/old/mia/mia considering.png differ diff --git a/game/dev/old/mia/mia explanatory.png b/game/dev/old/mia/mia explanatory.png new file mode 100644 index 0000000..04ea332 Binary files /dev/null and b/game/dev/old/mia/mia explanatory.png differ diff --git a/game/dev/old/mia/mia happy.png b/game/dev/old/mia/mia happy.png new file mode 100644 index 0000000..f70111d Binary files /dev/null and b/game/dev/old/mia/mia happy.png differ diff --git a/game/dev/old/mia/mia neutral.png b/game/dev/old/mia/mia neutral.png new file mode 100644 index 0000000..72360ba Binary files /dev/null and b/game/dev/old/mia/mia neutral.png differ diff --git a/game/dev/old/mia/mia sad.png b/game/dev/old/mia/mia sad.png new file mode 100644 index 0000000..e2e859c Binary files /dev/null and b/game/dev/old/mia/mia sad.png differ diff --git a/game/dev/old/mia/mia shock.png b/game/dev/old/mia/mia shock.png new file mode 100644 index 0000000..6b09181 Binary files /dev/null and b/game/dev/old/mia/mia shock.png differ diff --git a/game/dev/old/mia/mia unimpressed.png b/game/dev/old/mia/mia unimpressed.png new file mode 100644 index 0000000..1cab6e5 Binary files /dev/null and b/game/dev/old/mia/mia unimpressed.png differ diff --git a/game/dev/old/olivia dad/olivia dad neutral.png b/game/dev/old/olivia dad/olivia dad neutral.png new file mode 100644 index 0000000..2edd1c0 Binary files /dev/null and b/game/dev/old/olivia dad/olivia dad neutral.png differ diff --git a/game/dev/old/olivia/olivia angry.png b/game/dev/old/olivia/olivia angry.png new file mode 100644 index 0000000..827ce17 Binary files /dev/null and b/game/dev/old/olivia/olivia angry.png differ diff --git a/game/dev/old/olivia/olivia considering.png b/game/dev/old/olivia/olivia considering.png new file mode 100644 index 0000000..f5ea98b Binary files /dev/null and b/game/dev/old/olivia/olivia considering.png differ diff --git a/game/dev/old/olivia/olivia explanatory.png b/game/dev/old/olivia/olivia explanatory.png new file mode 100644 index 0000000..b3ef7f8 Binary files /dev/null and b/game/dev/old/olivia/olivia explanatory.png differ diff --git a/game/dev/old/olivia/olivia happy.png b/game/dev/old/olivia/olivia happy.png new file mode 100644 index 0000000..7aea284 Binary files /dev/null and b/game/dev/old/olivia/olivia happy.png differ diff --git a/game/dev/old/olivia/olivia neutral.png b/game/dev/old/olivia/olivia neutral.png new file mode 100644 index 0000000..dba7276 Binary files /dev/null and b/game/dev/old/olivia/olivia neutral.png differ diff --git a/game/dev/old/olivia/olivia sad.png b/game/dev/old/olivia/olivia sad.png new file mode 100644 index 0000000..68897ee Binary files /dev/null and b/game/dev/old/olivia/olivia sad.png differ diff --git a/game/dev/old/olivia/olivia shock.png b/game/dev/old/olivia/olivia shock.png new file mode 100644 index 0000000..6b89e71 Binary files /dev/null and b/game/dev/old/olivia/olivia shock.png differ diff --git a/game/dev/old/olivia/olivia unimpressed.png b/game/dev/old/olivia/olivia unimpressed.png new file mode 100644 index 0000000..ecefe5b Binary files /dev/null and b/game/dev/old/olivia/olivia unimpressed.png differ diff --git a/game/dev/old/oliviadad/oliviadad neutral.png b/game/dev/old/oliviadad/oliviadad neutral.png new file mode 100644 index 0000000..2edd1c0 Binary files /dev/null and b/game/dev/old/oliviadad/oliviadad neutral.png differ diff --git a/game/dev/old/scaler/scaler angry.png b/game/dev/old/scaler/scaler angry.png new file mode 100644 index 0000000..936afb6 Binary files /dev/null and b/game/dev/old/scaler/scaler angry.png differ diff --git a/game/dev/old/scaler/scaler considering.png b/game/dev/old/scaler/scaler considering.png new file mode 100644 index 0000000..ab6f657 Binary files /dev/null and b/game/dev/old/scaler/scaler considering.png differ diff --git a/game/dev/old/scaler/scaler happy.png b/game/dev/old/scaler/scaler happy.png new file mode 100644 index 0000000..a9e4cf3 Binary files /dev/null and b/game/dev/old/scaler/scaler happy.png differ diff --git a/game/dev/old/scaler/scaler neutral.png b/game/dev/old/scaler/scaler neutral.png new file mode 100644 index 0000000..d546422 Binary files /dev/null and b/game/dev/old/scaler/scaler neutral.png differ diff --git a/game/dev/poopenfarten_-_olivia.ogg b/game/dev/poopenfarten_-_olivia.ogg new file mode 100644 index 0000000..8fb6b06 Binary files /dev/null and b/game/dev/poopenfarten_-_olivia.ogg differ diff --git a/game/dev/test_definitions.rpy b/game/dev/test_definitions.rpy index 699f696..fcd25d3 100644 --- a/game/dev/test_definitions.rpy +++ b/game/dev/test_definitions.rpy @@ -8,9 +8,9 @@ image orbiva_head smile = "dev/test png/orbiva_head smile.png" # Add more dicts to cover other characters, or maybe includ more tags in the entry, idk python scares me. # This doesn't work if this is defined elsewhere, which it is in character.rpy -define config.adjust_attributes = { - "orbiva" : orbiva_expressions -} +#define config.adjust_attributes = { +# "orbiva" : orbiva_expressions +#} init -1 python: def orbiva_expressions(attrs): diff --git a/game/dev/uncropped sprites/_olivia pool neutral.png b/game/dev/uncropped sprites/_olivia pool neutral.png new file mode 100644 index 0000000..262fab8 Binary files /dev/null and b/game/dev/uncropped sprites/_olivia pool neutral.png differ diff --git a/game/dev/uncropped sprites/ferris neutral.png b/game/dev/uncropped sprites/ferris neutral.png new file mode 100644 index 0000000..7fdb63c Binary files /dev/null and b/game/dev/uncropped sprites/ferris neutral.png differ diff --git a/game/dev/uncropped sprites/house2.png b/game/dev/uncropped sprites/house2.png new file mode 100644 index 0000000..1e1d988 Binary files /dev/null and b/game/dev/uncropped sprites/house2.png differ diff --git a/game/dev/uncropped sprites/liz happy.png b/game/dev/uncropped sprites/liz happy.png new file mode 100644 index 0000000..baf3708 Binary files /dev/null and b/game/dev/uncropped sprites/liz happy.png differ diff --git a/game/dev/uncropped sprites/liz neutral.png b/game/dev/uncropped sprites/liz neutral.png new file mode 100644 index 0000000..f0a9e6c Binary files /dev/null and b/game/dev/uncropped sprites/liz neutral.png differ diff --git a/game/dev/uncropped sprites/liz sad.png b/game/dev/uncropped sprites/liz sad.png new file mode 100644 index 0000000..7263377 Binary files /dev/null and b/game/dev/uncropped sprites/liz sad.png differ diff --git a/game/dev/uncropped sprites/liz shocked.png b/game/dev/uncropped sprites/liz shocked.png new file mode 100644 index 0000000..72d4158 Binary files /dev/null and b/game/dev/uncropped sprites/liz shocked.png differ diff --git a/game/dev/uncropped sprites/liz thinking.png b/game/dev/uncropped sprites/liz thinking.png new file mode 100644 index 0000000..2d725b2 Binary files /dev/null and b/game/dev/uncropped sprites/liz thinking.png differ diff --git a/game/drawings/drawings.rpy b/game/drawings/drawings.rpy new file mode 100644 index 0000000..1b9af92 --- /dev/null +++ b/game/drawings/drawings.rpy @@ -0,0 +1 @@ +#Your masterpieces will be saved here. \ No newline at end of file diff --git a/game/gui.rpy b/game/gui.rpy index 270413d..9fb2a83 100644 --- a/game/gui.rpy +++ b/game/gui.rpy @@ -37,7 +37,7 @@ define gui.idle_color = '#ffffff' ## The small color is used for small text, which needs to be brighter/darker to ## achieve the same effect. -define gui.idle_small_color = '#00E1FF' +define gui.idle_small_color = '#ffffff' ## The color that is used for buttons and bars that are hovered. define gui.hover_color = '#fbff18' @@ -365,6 +365,9 @@ define gui.unscrollable = "hide" ## The number of blocks of dialogue history Ren'Py will keep. define config.history_length = 250 +## Additional space to add between history screen entries. +define gui.history_spacing = 0 + ## The height of a history screen entry, or None to make the height variable at ## the cost of performance. define gui.history_height = 210 diff --git a/game/gui/bubble.png b/game/gui/bubble.png new file mode 100644 index 0000000..3b23ff3 Binary files /dev/null and b/game/gui/bubble.png differ diff --git a/game/gui/button/menubuttons/check.png b/game/gui/button/menubuttons/check.png new file mode 100644 index 0000000..fad09ab Binary files /dev/null and b/game/gui/button/menubuttons/check.png differ diff --git a/game/gui/button/menubuttons/checkbox.png b/game/gui/button/menubuttons/checkbox.png new file mode 100644 index 0000000..962a8c4 Binary files /dev/null and b/game/gui/button/menubuttons/checkbox.png differ diff --git a/game/gui/button/menubuttons/cross.png b/game/gui/button/menubuttons/cross.png new file mode 100644 index 0000000..49b72b2 Binary files /dev/null and b/game/gui/button/menubuttons/cross.png differ diff --git a/game/gui/button/menubuttons/down.png b/game/gui/button/menubuttons/down.png new file mode 100644 index 0000000..b921b86 Binary files /dev/null and b/game/gui/button/menubuttons/down.png differ diff --git a/game/gui/button/menubuttons/up.png b/game/gui/button/menubuttons/up.png new file mode 100644 index 0000000..cd47432 Binary files /dev/null and b/game/gui/button/menubuttons/up.png differ diff --git a/game/gui/mod_frame.png b/game/gui/mod_frame.png new file mode 100644 index 0000000..8d34ba6 Binary files /dev/null and b/game/gui/mod_frame.png differ diff --git a/game/gui/phone_assets/background.png b/game/gui/phone_assets/background.png new file mode 100644 index 0000000..006ba0c Binary files /dev/null and b/game/gui/phone_assets/background.png differ diff --git a/game/gui/phone_assets/default_icon.png b/game/gui/phone_assets/default_icon.png new file mode 100644 index 0000000..a655c3f Binary files /dev/null and b/game/gui/phone_assets/default_icon.png differ diff --git a/game/gui/phone_assets/status_bar_battery_empty.png b/game/gui/phone_assets/status_bar_battery_empty.png new file mode 100644 index 0000000..da56d54 Binary files /dev/null and b/game/gui/phone_assets/status_bar_battery_empty.png differ diff --git a/game/gui/thoughtbubble.png b/game/gui/thoughtbubble.png new file mode 100644 index 0000000..8d5d44b Binary files /dev/null and b/game/gui/thoughtbubble.png differ diff --git a/game/mods/-DISABLEALLMODS.txt b/game/mods/-DISABLEALLMODS.txt new file mode 100644 index 0000000..e69de29 diff --git a/game/mods/-NOLOADORDER.txt b/game/mods/-NOLOADORDER.txt new file mode 100644 index 0000000..e69de29 diff --git a/game/mods/-NOMODS.txt b/game/mods/-NOMODS.txt new file mode 100644 index 0000000..e69de29 diff --git a/game/mods/README.md b/game/mods/README.md index 478b583..1d05934 100644 --- a/game/mods/README.md +++ b/game/mods/README.md @@ -1,55 +1,125 @@ -To normal users installing multiple mods: It's recommended to only have one mod folder installed in the mods directory in case two mods have duplicate `green_fang_story` label, otherwise the game will error out if mods conflict each other, this includes not only labels but images & sound too. +--- MOD LOADER USAGE --- -The game loads an alternate storyline.rpy, this allows you to control the flow of the game's storytelling -Examples include: -- You want to inject more stuff inbetween chapters, maybe you hate time skip writing -- You want to have more of an after story kind of ordeal, for example expanding Ending 2 -- You want to replace the entire story route -You can still call the vanilla game's chapters like the intro (call chapter_1) for example but you might want to either copy the vanilla scripts and mix in your edits to have full control +If there's problems with installed mods - like if an enabled mod makes the game crash on startup - there's 3 file flags you can apply to work around it, checked only in the root of the mods folder: +- Any file starting with "DISABLEALLMODS". This will force disable all currently installed mods, but will still load all their metadata. Removing this flag will return the mod states to how it was previously. +- Any file starting with "NOLOADORDER". This will erase the load order/states and always load mods in folder alphabetical order, with mod states depending on the "Enable New Mods" option in the preferences menu. +- Any file starting with "NOMODS". This turns off mod loading entirely by making the game not find any metadata files to load. +These file flags already exist in the mods folder, but renamed to not trigger in-game. Enable them by renaming the file to take off the first hyphen. -You will need to learn bit of Ren'Py & bit of actual Python anyways but you don't have to think too hard in learning anything new other than familiarizing with this game's script.rpy's already defined Character objects and images, you can freely ignore most of the UI and so on for the inexperienced but passionate artist and/or writer. +When ordering the mods in the mod menu, know that not all mod code will be loaded according to the order. Ren'Py has a feature called 'init' that will run code at certain mod-defined stages (priorities) of the engine starting up, so if one mod's init block is set to run at an earlier priority than another mod's, it doesn't what order it is in the mod loader, it will always load that init first. The only time when the order comes into effect is if 2 mod's init blocks run at the same priority, or aren't running in an init block. -Textbox limitation: ~300 characters / four lines, the maximum in the vanilla game's script is around roughly ~278 and that only barely overflows to four lines, so <200 characters / three lines of text should be fine. ---- Ideal file structure of your mod --- +--- MOD CREATION PRE-REQUSITES --- + +Before modding, you may want to do either of these things: + +-Set Wani as a renpy project and launch through the SDK so you can have easy access to debugging and other QoL tools, including dev mode. (Follow this link for directions https://git.cavemanon.xyz/Cavemanon/IWaniHugThatGator-Demo-Public) +-Open script.rpy and put 'config.developer = True' somewhere in the 'init python' block to have access to renpy's dev mode. + +Pick the latter option if you're lazy, since you don't seriously need to use the SDK for most things. + + +--- MOD CRREATION --- + +When creating a mod, make a new folder within the 'mods' directory at the root of the game directory and name it whatever you want. Inside that folder, make a file called 'metadata.json', and follow the JSON file format to implement details about your mod. An example would be: + +``` +{ + "ID": "234234u9SDjjasdhl23", + "Name": "Test Mod", + "Label": "test_mod_label", + "Version": "1.0", + "Authors": [ "Author1", "Author2", "Author3" ], + "Links": "Link1", + "Description: "This contains the description of my mod" +} +``` + +Make sure there isn't a comma at the end of the last entry in your JSON. + +Below is all the possible entries you can put in, and explanations for what they do. Note that you don't need to put all of these in your metadata file, and infact the only hard requirement is the "ID" entry. + + "ID" : The ID of your mod. Required to be able to load your mod at all, as it is used by the mod loader for mod orders and enabling/disabling. Make this as unique of a string as you can, like a hash. Smash your keyboard if you must. This is how the game knows to differentiate your mod from others (And can be used by other mods to find if a user has your mod installed, if they so choose). + + "Name" : The name of your mod. If this doesn't exist, the game will assume the name of your mod folder. + + "Label" : The label to jump to start your mod story. If this doesn't exist, the button this mod will appear as will do nothing when clicked. Useful if you're only modifying something relating to the base game. + + "Version" : The version number of your mod. + + "Authors" : The authors of your mod. This can be a list of strings or just a string. If it's just a string, it will display in the mod details pane with only "Author:" instead of "Authors:" + + "Links" : The links to download your mod and/or advertisement. This can be a list of strings or just a string. If it's just a string, it will display in the mod details pane with only "Link:" instead of "Links:". In order for this to be useful, use the 'a' text tag to make these texts hyperlinks. + + "Description" : The description of your mod. + + "Mobile Description" : The description of your mod, but only appearing while playing Wani on Android. If this doesn't exist, it will assume the contents of the description entry. Otherwise, you can copy your description text here and format it however you think it fits for Android. + + "Display" : How your mod button appears if there's an icon image detected. This can be set to "name" - which only displays the mod name - "icon" - which only displays the icon, taking up the entire button - or "both" - which displays the name and icon together, with the icon miniaturized and to the side of the name. This defaults to "both" if it doesn't exist, and if an icon image is not present, it will fall back to "name" mode. + + "Thumbnail Displayable" : What displayable to use for the thumbnail when the mod has loaded scripts. If this doesn't exist, the game will use the thumbnail image found alongside your metadata file + + "Icon Displayable" : What displayable to use for the icon when the mod has loaded scripts. If this doesn't exist, the game will use the icon image found alongside your metadata file + + "Screenshot Displayables" : What displayables to use for screenshots when the mod has loaded scripts. This should be a list of strings. The game will choose each displayable in the list over images found alongside the metadata file sequentially, so if there's 5 screenshot images and 3 screenshot displayables, the last 2 will still display the screenshot images, and the first 3 will display the screenshot displayables. If there's more displayables than images, then the mod will appear to gain screenshots in the mod details pane when the mod is enabled. If you enter empty strings in the list ( "" ), the game will interpret that as a deliberate skipping over to load screenshot images instead of displayables, so if your list consists of '[ "", "my_displayable" ]' and there's any number of screenshot images found, the first screenshot will still show a screenshot image, and only the next one will show a displayable. If this entry doesn't exist, it will just use the screenshot images found alongside your metadata file. + +In the same directory as your metadata file, there's image files you can put in the to make your mod more appealing. These can be any of Ren'Py's supported image file types, so the file extension here is just for demonstration: + + -'thumbnail.png' will appear as a banner for your mod, at the top of the mod details pane + -'icon.png' will show a small image next your mod name or take up the entire button depending on what your "Display" entry is set to. + -'screenshot(number).png' will show screenshots at the bottom of the mod details pane. The '(number)' is a placeholder for a number that represents what order your screenshots appear in. For example, you can have 'screenshot1.png', 'screenshot2.png', and 'screenshot3.png' in your mod directory, and they will all appear in the mod details pane in order. These numbers don't need to be strictly sequential, they can be any number as long as they are integers, and the order will be derived from ASCII ordering. + +As the game loads the metadata, it will also collect scripts for loading. When you make your mod scripts, ALL OF THEM NEED TO HAVE .rpym EXTENSIONS. This is important for being able to control mods with the mod loader, otherwise there would be mod conflicts galore. If you use .rpy extensions to make your mod scripts, they will work, but they will not be manageable by the mod loader. This means you should only use them for development, or if you're not using the mod loader anyway - such as adding a translation to the base game. + +Additionally, Ren'Py will not load any files from the mods folder automatically, so any and all audio/image/video files need to be manually defined to be usable. + +--- TRANSLATION --- + +For disambiguation, a "language code" refers to the code Ren'Py uses to refer to languages program-wide, and can be found on the folder names in "game/tl", with "None" being the default language, English (And internally being an actual None variable) + +For translating mods, you should use the Ren'Py SDK to automatically generate translations from mod scripts. Make sure to use a language code that Wani supports when you do so, so you don't accidentally create a new language. Put them into an organized directory in your mod and change them to use .rpym extensions so that translations don't activate unless your mod is as well (This prevents potential conflicts from other mods from interface/string translations). + +For assets, the easiest way is to put them in the "tl" folder and have the same filepath to your mod asset as it is from the "game" folder to your asset, just like how the officially supported translations are. This will make your mod less portable, but at least there's no namespace or file conflicts for translating images. If you're deadset on making your mod portable, you'll have to make conditionals in your mod to swap out images depending on the language, which means other people translating your mod can't just add a file somewhere and be done with it, they will need to edit your mod scripts as well (This isn't a problem with steam workshop mods since they don't need to do this, they can keep images in the "tl" folder). + +For translating metadata, you can translate the .json file by creating a new .json and naming it "metadata_(language code).json" (Ex: "metadata_es.json" for spanish). Fill out the .json how you would with the normal metadata file, but with your translated strings. The game will automatically pick this up and replace the strings (or add, if there's no english variant of an entry) according to your language. Note that translating the ID or Label will do nothing, so you will always need a metadata.json file with at least an ID entry even if your mod isn't in english. + +For the images found alongisde your metadata, you can do the same thing as your .json to replace them. Simply append your language code to your image filename like "thumbnail_es.png", "icon_es.png", etc.. For screenshots, the number in your filename will determine if any given translated image will replace another image, or add it inbetween images. So if you have "screenshot4.png", and you have "screenshot2_es.png" and "screenshot5_es.png", all screenshots will show up if your language is in spanish. If there's "screenshot4_es.png" in the directory, then it will replace "screenshot4.png". The final screenshot display will always take the english screenshots as a base before replacing the images with whatever translated images that exist. + + +--- UPLOADING TO THE STEAM WORKSHOP --- + +Consult the readme.md in the "wani workshop" folder. + + +--- TIPS --- + +The Ren'Py documentation is your friend, but it is also a bitch-ass friend. It will sometimes be notoriously unhelpful, so consult other renpy dudes from the Ren'Py discord, your snoot communities, or youtube. This may also be of interest, as it will link to other documentation as well as many interesting libraries you can use: + https://github.com/methanoliver/awesome-renpy + +Link to Ren'Py documentation: + https://www.renpy.org/doc/html/ + +When making the file structure for your mod, this is the ideal. Keep it nice and organized, preferably keeping the root of your mods folder clear of eveything but metadata related stuff: +``` In the root of the mods folder: folder_of_your_mod_name - - name_of_storyline.rpy + - metadata.json + - Your various image metadatas -> asset_folder - asset.png -> script_folder - - script.rpy - - name_of_storyline.rpy + - script.rpym + -> etc. folders... + - etc. files... ``` -init python: - # Modding Support variables - # All mod rpy files must have title of their mod (this shows up on a button) - # and finally the label that controls the flow of dialogue - - mod_menu_access += [{ - 'Name': "Mod Name", - 'Label': "mod_storyline" - }]; - -image template_sample = Image("mods/folder_of_your_mod_name/asset_folder/asset.png") - -label mod_storyline: - call chapter_1_new +To start your mod, just make it under the label you defined in your metadata file in a mod script. An example in a newly created "mymodscript.rpym": +``` + label my_mod_label: + scene my_background + show my_sprite + "blah blah dialogue" ``` - script_folder/script.rpy -``` -label chapter_1_new: - show template_sample at scenter - "Sample Text" +Textbox limitation: ~300 characters / four lines, the maximum in the vanilla game's script is around roughly ~278 and that only barely overflows to four lines, so <200 characters / three lines of text should be fine. - hide template_sample - play music 'audio/OST/Those Other Two Weirdos.ogg' - show anon neutral flip at aright with dissolve - A "Sample Text" - - return -``` - -The funny thing is I don't even like 'fanfictions' to begin with but this mod support allows these 'fanfictions', ironic. +If a user has many mods installed, it would be annoying to need to keep track of which mods to enable or disable because of code conflicts, so make your variables/functions unique. Since Ren'Py's own scripting doesn't support namespaced stuff, a very simple way is to prefix all your variables/functions with a mod-unique name, similar to your mod ID. An example would be "mymodisfantastic2349234_function_or_variable". Kinda ugly, but manageable with Ctrl-F and string replacement if there's problems. If there must be code conflicts, let the user know in your mod description! \ No newline at end of file diff --git a/game/mods/workshop uploader/Linux/libsteam_api.so b/game/mods/workshop uploader/Linux/libsteam_api.so new file mode 100644 index 0000000..8bf6762 Binary files /dev/null and b/game/mods/workshop uploader/Linux/libsteam_api.so differ diff --git a/game/mods/workshop uploader/Linux/wani_workshop b/game/mods/workshop uploader/Linux/wani_workshop new file mode 100644 index 0000000..c8e8ae8 Binary files /dev/null and b/game/mods/workshop uploader/Linux/wani_workshop differ diff --git a/game/mods/workshop uploader/MacOS/libsteam_api.dylib b/game/mods/workshop uploader/MacOS/libsteam_api.dylib new file mode 100644 index 0000000..8d3c5ee Binary files /dev/null and b/game/mods/workshop uploader/MacOS/libsteam_api.dylib differ diff --git a/game/mods/workshop uploader/MacOS/wani_workshop b/game/mods/workshop uploader/MacOS/wani_workshop new file mode 100644 index 0000000..0ceed84 Binary files /dev/null and b/game/mods/workshop uploader/MacOS/wani_workshop differ diff --git a/game/mods/workshop uploader/Windows/steam_api64.dll b/game/mods/workshop uploader/Windows/steam_api64.dll new file mode 100644 index 0000000..e1ca692 Binary files /dev/null and b/game/mods/workshop uploader/Windows/steam_api64.dll differ diff --git a/game/mods/workshop uploader/Windows/wani_workshop.exe b/game/mods/workshop uploader/Windows/wani_workshop.exe new file mode 100644 index 0000000..7e01cbe Binary files /dev/null and b/game/mods/workshop uploader/Windows/wani_workshop.exe differ diff --git a/game/mods/workshop uploader/readme.md b/game/mods/workshop uploader/readme.md new file mode 100644 index 0000000..bb175dd --- /dev/null +++ b/game/mods/workshop uploader/readme.md @@ -0,0 +1,6 @@ +This program is a fork of 4onen's Angels with Scaly Wings Workshop uploader, you can find it [here](https://github.com/4onen/AwSW-Workshop-Uploader/ "here"). + +You can find the source code for the uploader in our gitea instance [here](https://git.cavemanon.xyz/Legalo/Workshop-Uploader "here") + +To use first create a mod (Or input the mod's ID if you want to update your mod), then fill each field with the corresponding data. When selecting the folder to upload just select your mod's base folder, unless you are using absolute path, in that case select a folder that will act as the game's 'game' folder. +Leave the rpym option enabled unless you need to do some rpy fuckery or have rpy files outside your mod's folder. This will allow your mod to be compatible with the mod loader. \ No newline at end of file diff --git a/game/options.rpy b/game/options.rpy index 71636f7..ed66260 100644 --- a/game/options.rpy +++ b/game/options.rpy @@ -44,12 +44,13 @@ define build.name = "WaniGame" ## Sounds and music ############################################################ -## These three variables control which mixers are shown to the player by -## default. Setting one of these to False will hide the appropriate mixer. +## These three variables control, among other things, which mixers are shown +## to the player by default. Setting one of these to False will hide the +## appropriate mixer. define config.has_sound = True define config.has_music = True -define config.has_voice = False +define config.has_voice = True define config.play_channel = "uisounds" @@ -197,6 +198,24 @@ init python: # Do not include dev folders on distribution build.classify('game/dev/**', None) build.classify('game/dev/.**', None) + build.classify('build_patch/*', None) + + #Development files need not apply + build.classify('README.md', None) + build.classify('renconstruct.toml', None) + build.classify('bundle.keystore', None) + build.classify('android.keystore', None) + build.classify('bundle.keystore.original', None) + build.classify('android.keystore.original', None) + build.classify('*dist/*', None) + + build.classify('game/mods/workshop uploader/**', 'steamstuff') + build.classify('game/mods/workshop uploader/**', None) + + build.package("steam", "zip", "windows mac linux renpy all steamstuff") + # build.package("market", "bare-zip", "windows mac linux renpy all") + + #Development files need not apply build.classify('README.md', None) diff --git a/game/screens.rpy b/game/screens.rpy index 98b4f5d..687045c 100644 --- a/game/screens.rpy +++ b/game/screens.rpy @@ -255,10 +255,6 @@ screen choice(items): for i in items: textbutton i.caption action i.action -## When this is true, menu captions will be spoken by the narrator. When false, -## menu captions will be displayed as empty buttons. -define config.narrator_menu = True - style choice_vbox is vbox style choice_button is button @@ -280,7 +276,7 @@ style choice_button is default: activate_sound "audio/ui/snd_ui_click.wav" style choice_button_text is default: - properties gui.button_text_properties("choice_button") + properties gui.text_properties("choice_button") style old_choice_button is default: properties gui.button_properties("choice_button") @@ -378,6 +374,7 @@ style quick_button: properties gui.button_properties("quick_button") style quick_button_text: + properties gui.text_properties("quick_button") color "#FFFFFF" hover_color gui.hover_color selected_color "#3affb3" @@ -487,7 +484,7 @@ style navigation_button: properties gui.button_properties("navigation_button") style navigation_button_text: - properties gui.button_text_properties("navigation_button") + properties gui.text_properties("navigation_button") ## Main Menu screen ############################################################ @@ -576,11 +573,12 @@ screen main_menu(): tag menu style_prefix "main_menu" + add gui.main_menu_background ## This empty frame darkens the main menu. frame: - pass + style "main_menu_frame" #if renpy.seen_image("big ending"): # style_prefix "main_menu_ex" @@ -710,11 +708,11 @@ style main_menu_ex_frame is main_menu_frame ## This lays out the basic common structure of a game menu screen. It's called ## with the screen title, and displays the background, title, and navigation. ## -## The scroll parameter can be None, or one of "viewport" or "vpgrid". When +## The scroll parameter can be None, or one of "viewport" or "vpgrid". ## This screen is intended to be used with one or more children, which are ## transcluded (placed) inside it. -screen game_menu(title, scroll=None, yinitial=0.0): +screen game_menu(title, scroll=None, yinitial=0.0, spacing=0): style_prefix "game_menu" @@ -751,6 +749,8 @@ screen game_menu(title, scroll=None, yinitial=0.0): side_yfill True vbox: + spacing spacing + transclude elif scroll == "vpgrid": @@ -766,6 +766,8 @@ screen game_menu(title, scroll=None, yinitial=0.0): side_yfill True + spacing spacing + transclude else: @@ -1010,6 +1012,15 @@ screen file_slots(title, flag=False): textbutton "[page]" activate_sound "audio/ui/snd_ui_click.wav" action FilePage(page) textbutton _(">") activate_sound "audio/ui/snd_ui_click.wav" action FilePageNext(max=9) + if config.has_sync: + if CurrentScreenName() == "save": + textbutton _("Upload Sync"): + action UploadSync() + xalign 0.5 + else: + textbutton _("Download Sync"): + action DownloadSync() + xalign 0.5 style page_label is gui_label @@ -1027,7 +1038,7 @@ style page_label: ypadding 5 style page_label_text: - text_align 0.5 + textalign 0.5 layout "subtitle" hover_color gui.hover_color @@ -1035,13 +1046,13 @@ style page_button: properties gui.button_properties("page_button") style page_button_text: - properties gui.button_text_properties("page_button") + properties gui.text_properties("page_button") style slot_button: properties gui.button_properties("slot_button") style slot_button_text: - properties gui.button_text_properties("slot_button") + properties gui.text_properties("slot_button") ## Preferences screen ########################################################## @@ -1063,18 +1074,12 @@ screen preferences(): spacing 5 box_wrap True - vbox: - style_prefix "radio" - label _("Display") - textbutton _("Window") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("display", "any window") - textbutton _("Fullscreen") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("display", "fullscreen") - - vbox: - style_prefix "radio" - label _("Rollback Side") - textbutton _("Disable") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("rollback side", "disable") - textbutton _("Left") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("rollback side", "left") - textbutton _("Right") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("rollback side", "right") + if renpy.variant("pc") or renpy.variant("web"): + vbox: + style_prefix "radio" + label _("Display") + textbutton _("Window") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("display", "any window") + textbutton _("Fullscreen") activate_sound "audio/ui/snd_ui_option_on.wav" action Preference("display", "fullscreen") vbox: style_prefix "check" label _("Naughty Stuff") @@ -1104,6 +1109,12 @@ screen preferences(): textbutton _("Enable Chapter Select") activate_sound "audio/ui/snd_ui_option_on.wav" action ToggleVariable("persistent.enable_chapter_select", True, False) textbutton _("Enable Debug Scores") activate_sound "audio/ui/snd_ui_option_on.wav" action ToggleVariable("persistent.enable_debug_scores", True, False) + vbox: + style_prefix "check" + label _("Mods") + textbutton _("Show Mod Screenshots") activate_sound "audio/ui/snd_ui_option_on.wav" action ToggleVariable("persistent.show_mod_screenshots", True, False) + textbutton _("Enable New Mods") activate_sound "audio/ui/snd_ui_option_on.wav" action ToggleVariable("persistent.newmods_default_state", True, False) + vbox: style_prefix "check" label _("Language") @@ -1118,7 +1129,7 @@ screen preferences(): scrollbars "vertical" for lang in languages: fixed: - xsize 300 + xsize 656 ysize 60 use lang_button(lang) @@ -1241,7 +1252,7 @@ style radio_button: foreground "gui/button/radio_[prefix_]foreground.png" style radio_button_text: - properties gui.button_text_properties("radio_button") + properties gui.text_properties("radio_button") style check_vbox: spacing gui.pref_button_spacing @@ -1251,7 +1262,7 @@ style check_button: foreground "gui/button/check_[prefix_]foreground.png" style check_button_text: - properties gui.button_text_properties("check_button") + properties gui.text_properties("check_button") style slider_slider: xsize 525 @@ -1262,7 +1273,7 @@ style slider_button: left_margin 15 style slider_button_text: - properties gui.button_text_properties("slider_button") + properties gui.text_properties("slider_button") style slider_vbox: xsize 675 @@ -1283,7 +1294,7 @@ screen history(): ## Avoid predicting this screen, as it can be very large. predict False - use game_menu(_("History"), scroll=("vpgrid" if gui.history_height else "viewport"), yinitial=1.0): + use game_menu(_("History"), scroll=("vpgrid" if gui.history_height else "viewport"), yinitial=1.0, spacing=gui.history_spacing): style_prefix "history" @@ -1316,7 +1327,7 @@ screen history(): ## This determines what tags are allowed to be displayed on the history screen. -define gui.history_allow_tags = set() +define gui.history_allow_tags = { "alt", "noalt", "rt", "rb", "art" } style history_window is empty @@ -1325,8 +1336,6 @@ style history_name is gui_label style history_name_text is gui_label_text style history_text is gui_text -style history_text is gui_text - style history_label is gui_label style history_label_text is gui_label_text @@ -1577,8 +1586,9 @@ screen help(): textbutton _("Gamepad") action SetScreenVariable("device", "gamepad") vbox: - yalign 0.5 - xpos 1100 + yalign 0.6 + xsize 1300 + xpos 1275 if device == "keyboard": use keyboard_help elif device == "mouse": @@ -1637,6 +1647,9 @@ screen keyboard_help(): label "V" text _("Toggles assistive {a=https://www.renpy.org/l/voicing}self-voicing{/a}.") + hbox: + label "Shift+A" + text _("Opens the accessibility menu.") screen mouse_help(): style_prefix "help" @@ -1650,10 +1663,10 @@ screen mouse_help(): hbox: label _("Right Click") - text _("Accesses the game menu. Also escapes the Gallery.") + text _("Accesses the game menu, escapes the Gallery, and skips to next choice when used on the Skip button.") hbox: - label _("Mouse Wheel Up\nClick Rollback Side") + label _("Mouse Wheel Up") text _("Rolls back to earlier dialogue.") hbox: @@ -1675,13 +1688,12 @@ screen gamepad_help(): label _("Right Shoulder") text _("Rolls forward to later dialogue.") - hbox: label _("D-Pad, Sticks") text _("Navigate the interface.") hbox: - label _("Start, Guide") + label _("Start, Guide, B/Right Button") text _("Accesses the game menu.") hbox: @@ -1702,16 +1714,17 @@ style help_button: xmargin 12 style help_button_text: - properties gui.button_text_properties("help_button") + properties gui.text_properties("help_button") size 40 style help_label: - xsize 375 + xsize 500 right_padding 30 style help_label_text: size gui.text_size xalign 1.0 + textalign 1.0 ## Achievement screen ############################################################## ## @@ -1832,13 +1845,14 @@ style confirm_frame: yalign .5 style confirm_prompt_text: + textalign 0.5 layout "subtitle" style confirm_button: properties gui.button_properties("confirm_button") style confirm_button_text: - properties gui.button_text_properties("confirm_button") + properties gui.text_properties("confirm_button") ## Skip indicator screen ####################################################### @@ -1965,7 +1979,7 @@ screen nvl(dialogue, items=None): use nvl_dialogue(dialogue) ## Displays the menu, if given. The menu may be displayed incorrectly if - ## config.narrator_menu is set to True, as it is above. + ## config.narrator_menu is set to True. for i in items: textbutton i.caption: @@ -2051,7 +2065,186 @@ style nvl_button: xanchor gui.nvl_button_xalign style nvl_button_text: - properties gui.button_text_properties("nvl_button") + properties gui.text_properties("nvl_button") + + +## Bubble screen ############################################################### +## +## The bubble screen is used to display dialogue to the player when using speech +## bubbles. The bubble screen takes the same parameters as the say screen, must +## create a displayable with the id of "what", and can create displayables with +## the "namebox", "who", and "window" ids. +## +## https://www.renpy.org/doc/html/bubble.html#bubble-screen + +screen bubble(who, what): + style_prefix "bubble" + + window: + id "window" + + if who is not None: + + window: + id "namebox" + style "bubble_namebox" + + text who: + id "who" + + text what: + id "what" + +style bubble_window is empty +style bubble_namebox is empty +style bubble_who is default +style bubble_what is default + +style bubble_window: + xpadding 30 + top_padding 5 + bottom_padding 5 + +style bubble_namebox: + xalign 0.5 + +style bubble_who: + xalign 0.5 + textalign 0.5 + color "#000" + +style bubble_what: + align (0.5, 0.5) + text_align 0.5 + layout "subtitle" + color "#000" + +define bubble.frame = Frame("gui/bubble.png", 55, 55, 55, 95) +define bubble.thoughtframe = Frame("gui/thoughtbubble.png", 55, 55, 55, 55) + +define bubble.properties = { + "bottom_left" : { + "window_background" : Transform(bubble.frame, xzoom=1, yzoom=1), + "window_bottom_padding" : 27, + }, + + "bottom_right" : { + "window_background" : Transform(bubble.frame, xzoom=-1, yzoom=1), + "window_bottom_padding" : 27, + }, + + "top_left" : { + "window_background" : Transform(bubble.frame, xzoom=1, yzoom=-1), + "window_top_padding" : 27, + }, + + "top_right" : { + "window_background" : Transform(bubble.frame, xzoom=-1, yzoom=-1), + "window_top_padding" : 27, + }, + + "thought" : { + "window_background" : bubble.thoughtframe, + } +} + +define bubble.expand_area = { + "bottom_left" : (0, 0, 0, 22), + "bottom_right" : (0, 0, 0, 22), + "top_left" : (0, 22, 0, 0), + "top_right" : (0, 22, 0, 0), + "thought" : (0, 0, 0, 0), +} + + +## Bubble screen ############################################################### +## +## The bubble screen is used to display dialogue to the player when using speech +## bubbles. The bubble screen takes the same parameters as the say screen, must +## create a displayable with the id of "what", and can create displayables with +## the "namebox", "who", and "window" ids. +## +## https://www.renpy.org/doc/html/bubble.html#bubble-screen + +screen bubble(who, what): + style_prefix "bubble" + + window: + id "window" + + if who is not None: + + window: + id "namebox" + style "bubble_namebox" + + text who: + id "who" + + text what: + id "what" + +style bubble_window is empty +style bubble_namebox is empty +style bubble_who is default +style bubble_what is default + +style bubble_window: + xpadding 30 + top_padding 5 + bottom_padding 5 + +style bubble_namebox: + xalign 0.5 + +style bubble_who: + xalign 0.5 + textalign 0.5 + color "#000" + +style bubble_what: + align (0.5, 0.5) + text_align 0.5 + layout "subtitle" + color "#000" + +define bubble.frame = Frame("gui/bubble.png", 55, 55, 55, 95) +define bubble.thoughtframe = Frame("gui/thoughtbubble.png", 55, 55, 55, 55) + +define bubble.properties = { + "bottom_left" : { + "window_background" : Transform(bubble.frame, xzoom=1, yzoom=1), + "window_bottom_padding" : 27, + }, + + "bottom_right" : { + "window_background" : Transform(bubble.frame, xzoom=-1, yzoom=1), + "window_bottom_padding" : 27, + }, + + "top_left" : { + "window_background" : Transform(bubble.frame, xzoom=1, yzoom=-1), + "window_top_padding" : 27, + }, + + "top_right" : { + "window_background" : Transform(bubble.frame, xzoom=-1, yzoom=-1), + "window_top_padding" : 27, + }, + + "thought" : { + "window_background" : bubble.thoughtframe, + } +} + +define bubble.expand_area = { + "bottom_left" : (0, 0, 0, 22), + "bottom_right" : (0, 0, 0, 22), + "top_left" : (0, 22, 0, 0), + "top_right" : (0, 22, 0, 0), + "thought" : (0, 0, 0, 0), +} + ## Bubble screen ############################################################### @@ -2153,11 +2346,23 @@ define bubble.expand_area = { ## Since a mouse may not be present, we replace the quick menu with a version ## that uses fewer and bigger buttons that are easier to touch. +screen quick_buttons(filename, label_functions): + variant ["mobile", "steam_deck"] + if notagoodtime_ui: + grid 4 1: + for l_f in label_functions: + use quick_button(filename, l_f[0], l_f[1], l_f[2]) + else: + for l_f in label_functions: + use quick_button(filename, l_f[0], l_f[1], l_f[2]) + #redefine function screen quick_button(filename, label, function, function2): variant ["mobile", "steam_deck"] if notagoodtime_ui: button: + xmaximum 124 + ymaximum 124 at transform: on hover: matrixcolor BrightnessMatrix(-0.15) * SaturationMatrix(0.6) @@ -2167,11 +2372,9 @@ screen quick_button(filename, label, function, function2): matrixcolor BrightnessMatrix(0.0) * SaturationMatrix(1.0) zoom 1.0 blur 0 - xsize 71 - ysize 71 action function - add filename zoom 1 xalign 0.5 yalign 0.5 - text label size 19 xalign 0.5 yalign 0.5 style "old_quick_button_text" + add filename xalign 0.5 yalign 0.5 zoom 1.75 + text label xalign 0.5 yalign 0.5 size 42 style "old_quick_button_text" else: button: xmaximum 124 @@ -2203,54 +2406,22 @@ screen quick_menu(): [ _("Menu"), ShowMenu(), NullAction() ] \ ] ) else: - window: - xpos 1.45 - ypos 0.977 - vbox: - if quick_menu: - at showSkip - else: - at hideSkip - - style_prefix "quick" - xalign 0.0 - yalign 0.0 - use quick_buttons("gui/button/uioptionbuttons/template_idle_old.png", - [ - [ _("Skip"), Skip(), Skip(fast=True, confirm=True) ], - [ _("Save"), ShowMenu('save'), NullAction() ], - [ _("Auto"), Preference("auto-forward", "toggle"), NullAction() ], - [ _("Load"), ShowMenu('load'), NullAction() ] - ] ) - -screen quick_menu(): - - ## Ensure this appears on top of other screens. - zorder 100 - window: - xpos 1.45 - ypos 0.977 - vbox: + hbox: if quick_menu: at showSkip else: at hideSkip - if notagoodtime_ui == False: - style_prefix "quick" - spacing 1 - $ quick_button_image = "gui/button/uioptionbuttons/template_idle.png" - else: - style_prefix "quick" - xalign 0.0 - yalign 0.0 - $ quick_button_image = "gui/button/uioptionbuttons/template_idle_old.png" - use quick_buttons(quick_button_image, - [ - [ _("Skip"), Skip(), Skip(fast=True, confirm=True) ], - [ _("Save"), ShowMenu('save'), NullAction() ], - [ _("Auto"), Preference("auto-forward", "toggle"), NullAction() ], - [ _("Load"), ShowMenu('load'), NullAction() ] - ] ) + spacing 28 + style_prefix "quick" + xalign 0.5 + yalign 0.975 + use quick_buttons("gui/button/uioptionbuttons/template_idle_old.png", + [ + [ _("Back"), Rollback(), NullAction() ], \ + [ _("Skip"), Skip(), Skip(fast=True, confirm=True) ], \ + [ _("Auto"), Preference("auto-forward", "toggle"), NullAction() ], \ + [ _("Menu"), ShowMenu(), NullAction() ] \ + ] ) screen extrasnavigation(): #Updates are removed (not even supported by Ren'Py) @@ -2335,14 +2506,13 @@ style vslider: base_bar Frame("gui/phone/slider/vertical_[prefix_]bar.png", gui.vslider_borders, tile=gui.slider_tile) thumb "gui/phone/slider/vertical_[prefix_]thumb.png" -style slider_pref_vbox: +style slider_vbox: variant ["mobile", "steam_deck"] xsize None -style slider_pref_slider: +style slider_slider: variant ["mobile", "steam_deck"] - xsize 900 - + xsize 580 screen preferences(): variant ["mobile", "steam_deck"] @@ -2378,14 +2548,14 @@ screen preferences(): cols 1 mousewheel True draggable True - xsize 300 - ysize 250 + xsize 600 + ysize 300 if len(languages)>4: scrollbars "vertical" for lang in languages: fixed: - xsize 300 - ysize 60 + xsize 580 + ysize 80 use lang_button(lang) vbox: style_prefix "check" @@ -2399,8 +2569,23 @@ screen preferences(): label _("Performance") textbutton _("Disable heavy text animations.") action [Function(onclick_audio, persistent.text_no_heavy_ATL), ToggleVariable("persistent.text_no_heavy_ATL", True, False)] + vbox: + style_prefix "check" + label _("Mods") + textbutton _("Show Mod Screenshots") activate_sound "audio/ui/snd_ui_option_on.wav" action ToggleVariable("persistent.show_mod_screenshots", True, False) + textbutton _("Enable New Mods") activate_sound "audio/ui/snd_ui_option_on.wav" action ToggleVariable("persistent.newmods_default_state", True, False) + + if not main_menu: + if persistent.enable_debug_scores: + $ monitor_story_variables(False) + $ monitor_story_variables(True) + else: + $ monitor_story_variables(False) + + hbox: style_prefix "slider" + spacing 20 vbox: diff --git a/game/script.rpy b/game/script.rpy index 47b2e9c..35a9b46 100644 --- a/game/script.rpy +++ b/game/script.rpy @@ -1,8 +1,6 @@ -init -1 python: - # Modding Support variables - # All mod rpy files must run a small init python script - mod_dir = "mods/" - mod_menu_access = [] +init -1000 python: + if persistent.newmods_default_state == None: + persistent.newmods_default_state = True init python: #import random @@ -17,34 +15,43 @@ init python: if persistent.scroll == True: config.keymap['dismiss'].append('mousedown_4') - elif persistent.scroll == None: - persistent.scroll = False - if persistent.lewd == None: - persistent.lewd = False + persistent.splashtype = random.randint(0,2000 - 1) - if persistent.autoup == None: - persistent.autoup = False +if config.developer: + default persistent.enable_debug_scores = True + default persistent.enable_chapter_select = True +else: + default persistent.enable_debug_scores = False + default persistent.enable_chapter_select = False - if persistent.chapter_select_override == None: - if config.developer: - persistent.enable_chapter_select = True - else: - persistent.enable_select_override = False +default persistent.scroll = False +default persistent.autoup = False +default persistent.lewd = False +default persistent.show_mod_screenshots = True - if persistent.enable_debug_scores == None: - if config.developer: - persistent.enable_debug_scores = True - else: - persistent.enable_debug_scores = False +# This is neccesary for all functions that use get_placement like the crowd and liz functions due to 8.2.0 switching the output to position() data types +# There's no reason not to have this off currently, otherwise it's an extra step in getting a position value out of it with the current setup of most of the functions +# and there's no need to have 'interpolatability' with values using ATL. +# If someone can prove me wrong and refactoring the functions are good, be my guest. +define config.mixed_position = False - # Possibly for metro - #renpy.add_layer('bg_3d', below='master') - -define config.gl2 = True +define config.defer_tl_scripts = True +label before_main_menu: + if not persistent.epilogue: + $ renpy.music.play("" + DEFAULT_MUSIC_FILEPATH + "mus_titlescreen" + DEFAULT_MUSIC_EXTENSION, channel="music", loop=True) + else: + $ renpy.music.play(DEFAULT_MUSIC_FILEPATH + "mus_complete" + DEFAULT_MUSIC_EXTENSION, channel="music", loop=True) + if (persistent.languaged_up is None): + $ preferences.set_volume('ui', config.default_sfx_volume) + $ persistent.languaged_up = True + call screen lang_sel + + $ grant_missing() + return #actually play the game label start: diff --git a/game/script/2-Second-Time's-the-Charm.rpy b/game/script/2-Second-Time's-the-Charm.rpy index 2e1d7e5..85a7e83 100644 --- a/game/script/2-Second-Time's-the-Charm.rpy +++ b/game/script/2-Second-Time's-the-Charm.rpy @@ -2531,7 +2531,6 @@ label chapter_2: subpixel True on hide: pause 5 - yoffset -150 "olivia left considering" with eidissolve pause 0.5 parallel: @@ -2943,7 +2942,6 @@ label chapter_2: ease_quad 0.75 ypos 0.1 + LIZHEADY_NEUTRAL ease_cubic 2.25 ypos LIZHEADY_NEUTRAL parallel: - yoffset LIZHEAD_YOFFSET "lizhead left" with eidissolve pause 0.5 alpha 0.0 @@ -2965,7 +2963,6 @@ label chapter_2: alpha 0.0 pause 0.5 alpha 1.0 - yoffset LIZHEAD_YOFFSET "lizhead right" with eidissolve pause 1.5 "lizhead left" with eidissolve @@ -2987,7 +2984,7 @@ label chapter_2: I "But yeah, I'm new here, so I need an account." show lizhead explaining left with eidissolve: - yoffset 0 alpha 1.0 + alpha 1.0 setxy(0.7 - LIZHEADX_NEUTRAL, LIZHEADY_NEUTRAL) L "Not a problem at all." @@ -3048,7 +3045,7 @@ label chapter_2: L "Yeah? How're you liking it here so far?" - I "I uh...well..." + I "{cps=10}I uh...{/cps}{w=.3} {cps=10}well...{/cps}" L "Students here usually jump to asking about what they're graduating here for so they can more easily form connections and collaborate, so excuse me if I might be going too fast..." @@ -3167,7 +3164,7 @@ label chapter_2: I "Yeah! How'd you know?" show lizhead at setx(0.65 - LIZHEADX_NEUTRAL), movexy(0.6 - LIZHEADX_NEUTRAL, LIZHEADY_NEUTRAL + 0.05) - show lizhead sad with eidissolve_nd + show lizhead sad left with eidissolve_nd L "Oh, boy. I'm sorry about that, Inco." diff --git a/game/script/3-Canvas-of-Opportunity.rpy b/game/script/3-Canvas-of-Opportunity.rpy index 2d60b38..e0a26bc 100644 --- a/game/script/3-Canvas-of-Opportunity.rpy +++ b/game/script/3-Canvas-of-Opportunity.rpy @@ -593,8 +593,8 @@ label chapter_3: "I lean my head to see the teacher looking down at{cps=5}...{/cps}" show inco at sety(0.1), movex(0.15) with None - show prockling annoyed left at moveinleft(0.85, m=0.35): - show olivia neutral right at moveinleft(0.6, m=0.35): + show prockling annoyed left at moveinleft(0.85, m=0.35) + show olivia neutral right at moveinleft(0.6, m=0.35) with eidissolve show inco surprised with eidissolve @@ -2829,7 +2829,6 @@ label chapter_3: show lizhead: easein_quad 0.75 xpos 0.52 ypos 0.05 - yoffset LIZHEAD_YOFFSET "lizhead considering" with eidissolve backmovex(0.42) @@ -2844,7 +2843,6 @@ label chapter_3: L "Damien,{w=.3} {cps=15}shh.{/cps}" show lizhead considering: - yoffset 0 quadmovexy(0.12 + LIZHEADX_NEUTRAL, LIZHEADY_NEUTRAL) with None show lizhead neutral @@ -3682,8 +3680,8 @@ label choice_DrawNoteHalfPipe: show olivia shockeddown t_none at hity() show spr_olivia_tail_normal_blurred behind olivia: + yoffset -150 setx(0.7) - xanchor 0.5 yoffset -150 linear 0.04 ypos 0.53 xpos 0.56 pause 0.02 diff --git a/game/script/4-Crocodile-Sunny-D.rpy b/game/script/4-Crocodile-Sunny-D.rpy index b24cc3a..17d5c3b 100644 --- a/game/script/4-Crocodile-Sunny-D.rpy +++ b/game/script/4-Crocodile-Sunny-D.rpy @@ -293,7 +293,7 @@ label chapter_4: window auto hide show inco neutral carrying_bag at moveinright(0.2) - show damien neutral left at moveinleft(0.7): + show damien neutral left at moveinleft(0.7) with eidissolve pause 0.25 @@ -1215,7 +1215,7 @@ label chapter_4: show damien at movex(0.6) with None - show olivia pool neutral left at moveinleft(0.9, 0.1): + show olivia pool neutral left at moveinleft(0.9, 0.1) show spr_poolside at moveinleft(0.0) with eidissolve diff --git a/game/tl/None/common.rpym b/game/tl/None/common.rpym index 698b6a4..f7719c4 100644 --- a/game/tl/None/common.rpym +++ b/game/tl/None/common.rpym @@ -49,75 +49,83 @@ translate None strings: old "decrease" new "decrease" - # renpy/common/00accessibility.rpy:138 + # renpy/common/00accessibility.rpy:120 + old "Accessibility Menu. Use up and down arrows to navigate, and enter to activate buttons and bars." + new "Accessibility Menu. Use up and down arrows to navigate, and enter to activate buttons and bars." + + # renpy/common/00accessibility.rpy:139 old "Font Override" new "Font Override" - # renpy/common/00accessibility.rpy:142 + # renpy/common/00accessibility.rpy:143 old "Default" new "Default" - # renpy/common/00accessibility.rpy:146 + # renpy/common/00accessibility.rpy:147 old "DejaVu Sans" new "DejaVu Sans" - # renpy/common/00accessibility.rpy:150 + # renpy/common/00accessibility.rpy:151 old "Opendyslexic" new "Opendyslexic" - # renpy/common/00accessibility.rpy:156 + # renpy/common/00accessibility.rpy:157 old "Text Size Scaling" new "Text Size Scaling" - # renpy/common/00accessibility.rpy:162 + # renpy/common/00accessibility.rpy:163 old "Reset" new "Reset" - # renpy/common/00accessibility.rpy:168 + # renpy/common/00accessibility.rpy:169 old "Line Spacing Scaling" new "Line Spacing Scaling" - # renpy/common/00accessibility.rpy:180 + # renpy/common/00accessibility.rpy:181 old "High Contrast Text" new "High Contrast Text" - # renpy/common/00accessibility.rpy:182 + # renpy/common/00accessibility.rpy:183 old "Enable" new "Enable" - # renpy/common/00accessibility.rpy:186 + # renpy/common/00accessibility.rpy:187 old "Disable" new "Disable" - # renpy/common/00accessibility.rpy:193 + # renpy/common/00accessibility.rpy:194 old "Self-Voicing" new "Self-Voicing" - # renpy/common/00accessibility.rpy:197 + # renpy/common/00accessibility.rpy:198 old "Off" new "Off" - # renpy/common/00accessibility.rpy:201 + # renpy/common/00accessibility.rpy:202 old "Text-to-speech" new "Text-to-speech" - # renpy/common/00accessibility.rpy:205 + # renpy/common/00accessibility.rpy:206 old "Clipboard" new "Clipboard" - # renpy/common/00accessibility.rpy:209 + # renpy/common/00accessibility.rpy:210 old "Debug" new "Debug" - # renpy/common/00accessibility.rpy:215 + # renpy/common/00accessibility.rpy:216 + old "Voice Volume" + new "Voice Volume" + + # renpy/common/00accessibility.rpy:224 old "Self-Voicing Volume Drop" new "Self-Voicing Volume Drop" - # renpy/common/00accessibility.rpy:224 + # renpy/common/00accessibility.rpy:235 old "The options on this menu are intended to improve accessibility. They may not work with all games, and some combinations of options may render the game unplayable. This is not an issue with the game or engine. For the best results when changing fonts, try to keep the text size the same as it originally was." new "The options on this menu are intended to improve accessibility. They may not work with all games, and some combinations of options may render the game unplayable. This is not an issue with the game or engine. For the best results when changing fonts, try to keep the text size the same as it originally was." - # renpy/common/00accessibility.rpy:229 + # renpy/common/00accessibility.rpy:240 old "Return" new "Return" @@ -273,683 +281,723 @@ translate None strings: old "{#month_short}Dec" new "{#month_short}Dec" - # renpy/common/00action_file.rpy:250 + # renpy/common/00action_file.rpy:258 old "%b %d, %H:%M" new "%b %d, %H:%M" - # renpy/common/00action_file.rpy:364 + # renpy/common/00action_file.rpy:395 old "Save slot %s: [text]" new "Save slot %s: [text]" - # renpy/common/00action_file.rpy:445 + # renpy/common/00action_file.rpy:480 old "Load slot %s: [text]" new "Load slot %s: [text]" - # renpy/common/00action_file.rpy:498 + # renpy/common/00action_file.rpy:533 old "Delete slot [text]" new "Delete slot [text]" - # renpy/common/00action_file.rpy:577 + # renpy/common/00action_file.rpy:612 old "File page auto" new "File page auto" - # renpy/common/00action_file.rpy:579 + # renpy/common/00action_file.rpy:614 old "File page quick" new "File page quick" - # renpy/common/00action_file.rpy:581 + # renpy/common/00action_file.rpy:616 old "File page [text]" new "File page [text]" - # renpy/common/00action_file.rpy:639 + # renpy/common/00action_file.rpy:674 old "Page {}" new "Page {}" - # renpy/common/00action_file.rpy:639 + # renpy/common/00action_file.rpy:674 old "Automatic saves" new "Automatic saves" - # renpy/common/00action_file.rpy:639 + # renpy/common/00action_file.rpy:674 old "Quick saves" new "Quick saves" - # renpy/common/00action_file.rpy:780 + # renpy/common/00action_file.rpy:815 old "Next file page." new "Next file page." - # renpy/common/00action_file.rpy:852 + # renpy/common/00action_file.rpy:887 old "Previous file page." new "Previous file page." - # renpy/common/00action_file.rpy:913 + # renpy/common/00action_file.rpy:948 old "Quick save complete." new "Quick save complete." - # renpy/common/00action_file.rpy:931 + # renpy/common/00action_file.rpy:963 old "Quick save." new "Quick save." - # renpy/common/00action_file.rpy:950 + # renpy/common/00action_file.rpy:982 old "Quick load." new "Quick load." - # renpy/common/00action_other.rpy:381 + # renpy/common/00action_other.rpy:383 old "Language [text]" new "Language [text]" - # renpy/common/00action_other.rpy:703 + # renpy/common/00action_other.rpy:746 old "Open [text] directory." new "Open [text] directory." - # renpy/common/00director.rpy:708 + # renpy/common/00director.rpy:712 old "The interactive director is not enabled here." new "The interactive director is not enabled here." - # renpy/common/00director.rpy:1481 + # renpy/common/00director.rpy:1511 old "⬆" new "⬆" - # renpy/common/00director.rpy:1487 + # renpy/common/00director.rpy:1517 old "⬇" new "⬇" - # renpy/common/00director.rpy:1551 + # renpy/common/00director.rpy:1581 old "Done" new "Done" - # renpy/common/00director.rpy:1561 + # renpy/common/00director.rpy:1591 old "(statement)" new "(statement)" - # renpy/common/00director.rpy:1562 + # renpy/common/00director.rpy:1592 old "(tag)" new "(tag)" - # renpy/common/00director.rpy:1563 + # renpy/common/00director.rpy:1593 old "(attributes)" new "(attributes)" - # renpy/common/00director.rpy:1564 + # renpy/common/00director.rpy:1594 old "(transform)" new "(transform)" - # renpy/common/00director.rpy:1589 + # renpy/common/00director.rpy:1619 old "(transition)" new "(transition)" - # renpy/common/00director.rpy:1601 + # renpy/common/00director.rpy:1631 old "(channel)" new "(channel)" - # renpy/common/00director.rpy:1602 + # renpy/common/00director.rpy:1632 old "(filename)" new "(filename)" - # renpy/common/00director.rpy:1631 + # renpy/common/00director.rpy:1661 old "Change" new "Change" - # renpy/common/00director.rpy:1633 + # renpy/common/00director.rpy:1663 old "Add" new "Add" - # renpy/common/00director.rpy:1636 + # renpy/common/00director.rpy:1666 old "Cancel" new "Cancel" - # renpy/common/00director.rpy:1639 + # renpy/common/00director.rpy:1669 old "Remove" new "Remove" - # renpy/common/00director.rpy:1674 + # renpy/common/00director.rpy:1704 old "Statement:" new "Statement:" - # renpy/common/00director.rpy:1695 + # renpy/common/00director.rpy:1725 old "Tag:" new "Tag:" - # renpy/common/00director.rpy:1711 + # renpy/common/00director.rpy:1741 old "Attributes:" new "Attributes:" - # renpy/common/00director.rpy:1729 + # renpy/common/00director.rpy:1752 + old "Click to toggle attribute, right click to toggle negative attribute." + new "Click to toggle attribute, right click to toggle negative attribute." + + # renpy/common/00director.rpy:1764 old "Transforms:" new "Transforms:" - # renpy/common/00director.rpy:1748 + # renpy/common/00director.rpy:1775 + old "Click to set transform, right click to add to transform list." + new "Click to set transform, right click to add to transform list." + + # renpy/common/00director.rpy:1776 + old "Customize director.transforms to add more transforms." + new "Customize director.transforms to add more transforms." + + # renpy/common/00director.rpy:1788 old "Behind:" new "Behind:" - # renpy/common/00director.rpy:1767 + # renpy/common/00director.rpy:1799 + old "Click to set, right click to add to behind list." + new "Click to set, right click to add to behind list." + + # renpy/common/00director.rpy:1811 old "Transition:" new "Transition:" - # renpy/common/00director.rpy:1785 + # renpy/common/00director.rpy:1821 + old "Click to set." + new "Click to set." + + # renpy/common/00director.rpy:1822 + old "Customize director.transitions to add more transitions." + new "Customize director.transitions to add more transitions." + + # renpy/common/00director.rpy:1834 old "Channel:" new "Channel:" - # renpy/common/00director.rpy:1803 + # renpy/common/00director.rpy:1845 + old "Customize director.audio_channels to add more channels." + new "Customize director.audio_channels to add more channels." + + # renpy/common/00director.rpy:1857 old "Audio Filename:" new "Audio Filename:" - # renpy/common/00gui.rpy:435 + # renpy/common/00gui.rpy:448 old "Are you sure?" new "Are you sure?" - # renpy/common/00gui.rpy:436 + # renpy/common/00gui.rpy:449 old "Are you sure you want to delete this save?" new "Are you sure you want to delete this save?" - # renpy/common/00gui.rpy:437 + # renpy/common/00gui.rpy:450 old "Are you sure you want to overwrite your save?" new "Are you sure you want to overwrite your save?" - # renpy/common/00gui.rpy:438 + # renpy/common/00gui.rpy:451 old "Loading will lose unsaved progress.\nAre you sure you want to do this?" new "Loading will lose unsaved progress.\nAre you sure you want to do this?" - # renpy/common/00gui.rpy:439 + # renpy/common/00gui.rpy:452 old "Are you sure you want to quit?" new "Are you sure you want to quit?" - # renpy/common/00gui.rpy:440 + # renpy/common/00gui.rpy:453 old "Are you sure you want to return to the main menu?\nThis will lose unsaved progress." new "Are you sure you want to return to the main menu?\nThis will lose unsaved progress." - # renpy/common/00gui.rpy:441 + # renpy/common/00gui.rpy:454 + old "Are you sure you want to continue where you left off?" + new "Are you sure you want to continue where you left off?" + + # renpy/common/00gui.rpy:455 old "Are you sure you want to end the replay?" new "Are you sure you want to end the replay?" - # renpy/common/00gui.rpy:442 + # renpy/common/00gui.rpy:456 old "Are you sure you want to begin skipping?" new "Are you sure you want to begin skipping?" - # renpy/common/00gui.rpy:443 + # renpy/common/00gui.rpy:457 old "Are you sure you want to skip to the next choice?" new "Are you sure you want to skip to the next choice?" - # renpy/common/00gui.rpy:444 + # renpy/common/00gui.rpy:458 old "Are you sure you want to skip unseen dialogue to the next choice?" new "Are you sure you want to skip unseen dialogue to the next choice?" - # renpy/common/00keymap.rpy:310 + # renpy/common/00gui.rpy:459 + old "This save was created on a different device. Maliciously constructed save files can harm your computer. Do you trust this save's creator and everyone who could have changed the file?" + new "This save was created on a different device. Maliciously constructed save files can harm your computer. Do you trust this save's creator and everyone who could have changed the file?" + + # renpy/common/00gui.rpy:460 + old "Do you trust the device the save was created on? You should only choose yes if you are the device's sole user." + new "Do you trust the device the save was created on? You should only choose yes if you are the device's sole user." + + # renpy/common/00keymap.rpy:323 old "Failed to save screenshot as %s." new "Failed to save screenshot as %s." - # renpy/common/00keymap.rpy:322 + # renpy/common/00keymap.rpy:335 old "Saved screenshot as %s." new "Saved screenshot as %s." - # renpy/common/00library.rpy:230 + # renpy/common/00library.rpy:248 old "Skip Mode" new "Skip Mode" - # renpy/common/00library.rpy:316 + # renpy/common/00library.rpy:317 old "This program contains free software under a number of licenses, including the MIT License and GNU Lesser General Public License. A complete list of software, including links to full source code, can be found {a=https://www.renpy.org/l/license}here{/a}." new "This program contains free software under a number of licenses, including the MIT License and GNU Lesser General Public License. A complete list of software, including links to full source code, can be found {a=https://www.renpy.org/l/license}here{/a}." - # renpy/common/00preferences.rpy:259 + # renpy/common/00preferences.rpy:288 old "display" new "display" - # renpy/common/00preferences.rpy:271 + # renpy/common/00preferences.rpy:308 old "transitions" new "transitions" - # renpy/common/00preferences.rpy:280 + # renpy/common/00preferences.rpy:317 old "skip transitions" new "skip transitions" - # renpy/common/00preferences.rpy:282 + # renpy/common/00preferences.rpy:319 old "video sprites" new "video sprites" - # renpy/common/00preferences.rpy:291 + # renpy/common/00preferences.rpy:328 old "show empty window" new "show empty window" - # renpy/common/00preferences.rpy:300 + # renpy/common/00preferences.rpy:337 old "text speed" new "text speed" - # renpy/common/00preferences.rpy:308 + # renpy/common/00preferences.rpy:345 old "joystick" new "joystick" - # renpy/common/00preferences.rpy:308 + # renpy/common/00preferences.rpy:345 old "joystick..." new "joystick..." - # renpy/common/00preferences.rpy:315 + # renpy/common/00preferences.rpy:352 old "skip" new "skip" - # renpy/common/00preferences.rpy:318 + # renpy/common/00preferences.rpy:355 old "skip unseen [text]" new "skip unseen [text]" - # renpy/common/00preferences.rpy:323 + # renpy/common/00preferences.rpy:360 old "skip unseen text" new "skip unseen text" - # renpy/common/00preferences.rpy:325 + # renpy/common/00preferences.rpy:362 old "begin skipping" new "begin skipping" - # renpy/common/00preferences.rpy:329 + # renpy/common/00preferences.rpy:366 old "after choices" new "after choices" - # renpy/common/00preferences.rpy:336 + # renpy/common/00preferences.rpy:373 old "skip after choices" new "skip after choices" - # renpy/common/00preferences.rpy:338 + # renpy/common/00preferences.rpy:375 old "auto-forward time" new "auto-forward time" - # renpy/common/00preferences.rpy:352 + # renpy/common/00preferences.rpy:389 old "auto-forward" new "auto-forward" - # renpy/common/00preferences.rpy:359 + # renpy/common/00preferences.rpy:396 old "Auto forward" new "Auto forward" - # renpy/common/00preferences.rpy:362 + # renpy/common/00preferences.rpy:399 old "auto-forward after click" new "auto-forward after click" - # renpy/common/00preferences.rpy:371 + # renpy/common/00preferences.rpy:408 old "automatic move" new "automatic move" - # renpy/common/00preferences.rpy:380 + # renpy/common/00preferences.rpy:417 old "wait for voice" new "wait for voice" - # renpy/common/00preferences.rpy:389 + # renpy/common/00preferences.rpy:426 old "voice sustain" new "voice sustain" - # renpy/common/00preferences.rpy:398 + # renpy/common/00preferences.rpy:435 old "self voicing" new "self voicing" - # renpy/common/00preferences.rpy:407 + # renpy/common/00preferences.rpy:438 + old "self voicing enable" + new "self voicing enable" + + # renpy/common/00preferences.rpy:440 + old "self voicing disable" + new "self voicing disable" + + # renpy/common/00preferences.rpy:444 old "self voicing volume drop" new "self voicing volume drop" - # renpy/common/00preferences.rpy:415 + # renpy/common/00preferences.rpy:452 old "clipboard voicing" new "clipboard voicing" - # renpy/common/00preferences.rpy:424 + # renpy/common/00preferences.rpy:455 + old "clipboard voicing enable" + new "clipboard voicing enable" + + # renpy/common/00preferences.rpy:457 + old "clipboard voicing disable" + new "clipboard voicing disable" + + # renpy/common/00preferences.rpy:461 old "debug voicing" new "debug voicing" - # renpy/common/00preferences.rpy:433 + # renpy/common/00preferences.rpy:464 + old "debug voicing enable" + new "debug voicing enable" + + # renpy/common/00preferences.rpy:466 + old "debug voicing disable" + new "debug voicing disable" + + # renpy/common/00preferences.rpy:470 old "emphasize audio" new "emphasize audio" - # renpy/common/00preferences.rpy:442 + # renpy/common/00preferences.rpy:479 old "rollback side" new "rollback side" - # renpy/common/00preferences.rpy:452 + # renpy/common/00preferences.rpy:489 old "gl powersave" new "gl powersave" - # renpy/common/00preferences.rpy:458 + # renpy/common/00preferences.rpy:495 old "gl framerate" new "gl framerate" - # renpy/common/00preferences.rpy:461 + # renpy/common/00preferences.rpy:498 old "gl tearing" new "gl tearing" - # renpy/common/00preferences.rpy:464 + # renpy/common/00preferences.rpy:501 old "font transform" new "font transform" - # renpy/common/00preferences.rpy:467 + # renpy/common/00preferences.rpy:504 old "font size" new "font size" - # renpy/common/00preferences.rpy:475 + # renpy/common/00preferences.rpy:512 old "font line spacing" new "font line spacing" - # renpy/common/00preferences.rpy:483 + # renpy/common/00preferences.rpy:520 old "system cursor" new "system cursor" - # renpy/common/00preferences.rpy:492 + # renpy/common/00preferences.rpy:529 old "renderer menu" new "renderer menu" - # renpy/common/00preferences.rpy:495 + # renpy/common/00preferences.rpy:532 old "accessibility menu" new "accessibility menu" - # renpy/common/00preferences.rpy:498 + # renpy/common/00preferences.rpy:535 old "high contrast text" new "high contrast text" - # renpy/common/00preferences.rpy:507 + # renpy/common/00preferences.rpy:544 old "audio when minimized" new "audio when minimized" - # renpy/common/00preferences.rpy:527 + # renpy/common/00preferences.rpy:553 + old "audio when unfocused" + new "audio when unfocused" + + # renpy/common/00preferences.rpy:562 + old "web cache preload" + new "web cache preload" + + # renpy/common/00preferences.rpy:577 + old "voice after game menu" + new "voice after game menu" + + # renpy/common/00preferences.rpy:586 + old "restore window position" + new "restore window position" + + # renpy/common/00preferences.rpy:595 + old "reset" + new "reset" + + # renpy/common/00preferences.rpy:608 old "main volume" new "main volume" - # renpy/common/00preferences.rpy:528 + # renpy/common/00preferences.rpy:609 old "music volume" new "music volume" - # renpy/common/00preferences.rpy:529 + # renpy/common/00preferences.rpy:610 old "sound volume" new "sound volume" - # renpy/common/00preferences.rpy:530 + # renpy/common/00preferences.rpy:611 old "voice volume" new "voice volume" - # renpy/common/00preferences.rpy:531 + # renpy/common/00preferences.rpy:612 old "mute main" new "mute main" - # renpy/common/00preferences.rpy:532 + # renpy/common/00preferences.rpy:613 old "mute music" new "mute music" - # renpy/common/00preferences.rpy:533 + # renpy/common/00preferences.rpy:614 old "mute sound" new "mute sound" - # renpy/common/00preferences.rpy:534 + # renpy/common/00preferences.rpy:615 old "mute voice" new "mute voice" - # renpy/common/00preferences.rpy:535 + # renpy/common/00preferences.rpy:616 old "mute all" new "mute all" - # renpy/common/00preferences.rpy:616 + # renpy/common/00preferences.rpy:698 old "Clipboard voicing enabled. Press 'shift+C' to disable." new "Clipboard voicing enabled. Press 'shift+C' to disable." - # renpy/common/00preferences.rpy:618 + # renpy/common/00preferences.rpy:700 old "Self-voicing would say \"[renpy.display.tts.last]\". Press 'alt+shift+V' to disable." new "Self-voicing would say \"[renpy.display.tts.last]\". Press 'alt+shift+V' to disable." - # renpy/common/00preferences.rpy:620 + # renpy/common/00preferences.rpy:702 old "Self-voicing enabled. Press 'v' to disable." new "Self-voicing enabled. Press 'v' to disable." - # renpy/common/_compat/gamemenu.rpym:198 - old "Empty Slot." - new "Empty Slot." + # renpy/common/00speechbubble.rpy:392 + old "Speech Bubble Editor" + new "Speech Bubble Editor" - # renpy/common/_compat/gamemenu.rpym:355 - old "Previous" - new "Previous" + # renpy/common/00speechbubble.rpy:397 + old "(hide)" + new "(hide)" - # renpy/common/_compat/gamemenu.rpym:362 - old "Next" - new "Next" + # renpy/common/00speechbubble.rpy:408 + old "(clear retained bubbles)" + new "(clear retained bubbles)" - # renpy/common/_compat/preferences.rpym:428 - old "Joystick Mapping" - new "Joystick Mapping" + # renpy/common/00sync.rpy:70 + old "Sync downloaded." + new "Sync downloaded." - # renpy/common/_developer/developer.rpym:38 - old "Developer Menu" - new "Developer Menu" + # renpy/common/00sync.rpy:190 + old "Could not connect to the Ren'Py Sync server." + new "Could not connect to the Ren'Py Sync server." - # renpy/common/_developer/developer.rpym:43 - old "Interactive Director (D)" - new "Interactive Director (D)" + # renpy/common/00sync.rpy:192 + old "The Ren'Py Sync server timed out." + new "The Ren'Py Sync server timed out." - # renpy/common/_developer/developer.rpym:45 - old "Reload Game (Shift+R)" - new "Reload Game (Shift+R)" + # renpy/common/00sync.rpy:194 + old "An unknown error occurred while connecting to the Ren'Py Sync server." + new "An unknown error occurred while connecting to the Ren'Py Sync server." - # renpy/common/_developer/developer.rpym:47 - old "Console (Shift+O)" - new "Console (Shift+O)" + # renpy/common/00sync.rpy:267 + old "The Ren'Py Sync server does not have a copy of this sync. The sync ID may be invalid, or it may have timed out." + new "The Ren'Py Sync server does not have a copy of this sync. The sync ID may be invalid, or it may have timed out." - # renpy/common/_developer/developer.rpym:49 - old "Variable Viewer" - new "Variable Viewer" + # renpy/common/00sync.rpy:412 + old "Please enter the sync ID you generated.\nNever enter a sync ID you didn't create yourself." + new "Please enter the sync ID you generated.\nNever enter a sync ID you didn't create yourself." - # renpy/common/_developer/developer.rpym:51 - old "Image Location Picker" - new "Image Location Picker" + # renpy/common/00sync.rpy:431 + old "The sync ID is not in the correct format." + new "The sync ID is not in the correct format." - # renpy/common/_developer/developer.rpym:53 - old "Filename List" - new "Filename List" + # renpy/common/00sync.rpy:451 + old "The sync could not be decrypted." + new "The sync could not be decrypted." - # renpy/common/_developer/developer.rpym:57 - old "Show Image Load Log (F4)" - new "Show Image Load Log (F4)" + # renpy/common/00sync.rpy:474 + old "The sync belongs to a different game." + new "The sync belongs to a different game." - # renpy/common/_developer/developer.rpym:60 - old "Hide Image Load Log (F4)" - new "Hide Image Load Log (F4)" + # renpy/common/00sync.rpy:479 + old "The sync contains a file with an invalid name." + new "The sync contains a file with an invalid name." - # renpy/common/_developer/developer.rpym:63 - old "Image Attributes" - new "Image Attributes" + # renpy/common/00sync.rpy:538 + old "This will upload your saves to the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}.\nDo you want to continue?" + new "This will upload your saves to the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}.\nDo you want to continue?" - # renpy/common/_developer/developer.rpym:90 - old "[name] [attributes] (hidden)" - new "[name] [attributes] (hidden)" + # renpy/common/00sync.rpy:546 + old "Yes" + new "Yes" - # renpy/common/_developer/developer.rpym:94 - old "[name] [attributes]" - new "[name] [attributes]" + # renpy/common/00sync.rpy:547 + old "No" + new "No" - # renpy/common/_developer/developer.rpym:143 - old "Nothing to inspect." - new "Nothing to inspect." + # renpy/common/00sync.rpy:569 + old "Enter Sync ID" + new "Enter Sync ID" - # renpy/common/_developer/developer.rpym:154 - old "Hide deleted" - new "Hide deleted" + # renpy/common/00sync.rpy:580 + old "This will contact the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}." + new "This will contact the {a=https://sync.renpy.org}Ren'Py Sync Server{/a}." - # renpy/common/_developer/developer.rpym:154 - old "Show deleted" - new "Show deleted" + # renpy/common/00sync.rpy:609 + old "Sync Success" + new "Sync Success" - # renpy/common/_developer/developer.rpym:278 - old "Return to the developer menu" - new "Return to the developer menu" + # renpy/common/00sync.rpy:612 + old "The Sync ID is:" + new "The Sync ID is:" - # renpy/common/_developer/developer.rpym:443 - old "Rectangle: %r" - new "Rectangle: %r" + # renpy/common/00sync.rpy:618 + old "You can use this ID to download your save on another device.\nThis sync will expire in an hour.\nRen'Py Sync is supported by {a=https://www.renpy.org/sponsors.html}Ren'Py's Sponsors{/a}." + new "You can use this ID to download your save on another device.\nThis sync will expire in an hour.\nRen'Py Sync is supported by {a=https://www.renpy.org/sponsors.html}Ren'Py's Sponsors{/a}." - # renpy/common/_developer/developer.rpym:448 - old "Mouse position: %r" - new "Mouse position: %r" + # renpy/common/00sync.rpy:622 + old "Continue" + new "Continue" - # renpy/common/_developer/developer.rpym:453 - old "Right-click or escape to quit." - new "Right-click or escape to quit." + # renpy/common/00sync.rpy:646 + old "Sync Error" + new "Sync Error" - # renpy/common/_developer/developer.rpym:485 - old "Rectangle copied to clipboard." - new "Rectangle copied to clipboard." - - # renpy/common/_developer/developer.rpym:488 - old "Position copied to clipboard." - new "Position copied to clipboard." - - # renpy/common/_developer/developer.rpym:506 - old "Type to filter: " - new "Type to filter: " - - # renpy/common/_developer/developer.rpym:633 - old "Textures: [tex_count] ([tex_size_mb:.1f] MB)" - new "Textures: [tex_count] ([tex_size_mb:.1f] MB)" - - # renpy/common/_developer/developer.rpym:637 - old "Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)" - new "Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)" - - # renpy/common/_developer/developer.rpym:647 - old "✔ " - new "✔ " - - # renpy/common/_developer/developer.rpym:650 - old "✘ " - new "✘ " - - # renpy/common/_developer/developer.rpym:655 - old "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}" - new "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}" - - # renpy/common/_developer/inspector.rpym:38 - old "Displayable Inspector" - new "Displayable Inspector" - - # renpy/common/_developer/inspector.rpym:61 - old "Size" - new "Size" - - # renpy/common/_developer/inspector.rpym:65 - old "Style" - new "Style" - - # renpy/common/_developer/inspector.rpym:71 - old "Location" - new "Location" - - # renpy/common/_developer/inspector.rpym:122 - old "Inspecting Styles of [displayable_name!q]" - new "Inspecting Styles of [displayable_name!q]" - - # renpy/common/_developer/inspector.rpym:139 - old "displayable:" - new "displayable:" - - # renpy/common/_developer/inspector.rpym:145 - old " (no properties affect the displayable)" - new " (no properties affect the displayable)" - - # renpy/common/_developer/inspector.rpym:147 - old " (default properties omitted)" - new " (default properties omitted)" - - # renpy/common/_developer/inspector.rpym:185 - old "" - new "" - - # renpy/common/_layout/classic_load_save.rpym:170 - old "a" - new "a" - - # renpy/common/_layout/classic_load_save.rpym:179 - old "q" - new "q" - - # renpy/common/00iap.rpy:219 + # renpy/common/00iap.rpy:231 old "Contacting App Store\nPlease Wait..." new "Contacting App Store\nPlease Wait..." - # renpy/common/00updater.rpy:419 - old "The Ren'Py Updater is not supported on mobile devices." - new "The Ren'Py Updater is not supported on mobile devices." + # renpy/common/00updater.rpy:505 + old "No update methods found." + new "No update methods found." - # renpy/common/00updater.rpy:548 + # renpy/common/00updater.rpy:552 + old "Could not download file list: " + new "Could not download file list: " + + # renpy/common/00updater.rpy:555 + old "File list digest does not match." + new "File list digest does not match." + + # renpy/common/00updater.rpy:763 old "An error is being simulated." new "An error is being simulated." - # renpy/common/00updater.rpy:738 + # renpy/common/00updater.rpy:951 old "Either this project does not support updating, or the update status file was deleted." new "Either this project does not support updating, or the update status file was deleted." - # renpy/common/00updater.rpy:752 + # renpy/common/00updater.rpy:965 old "This account does not have permission to perform an update." new "This account does not have permission to perform an update." - # renpy/common/00updater.rpy:755 + # renpy/common/00updater.rpy:968 old "This account does not have permission to write the update log." new "This account does not have permission to write the update log." - # renpy/common/00updater.rpy:783 + # renpy/common/00updater.rpy:1048 old "Could not verify update signature." new "Could not verify update signature." - # renpy/common/00updater.rpy:1084 + # renpy/common/00updater.rpy:1367 old "The update file was not downloaded." new "The update file was not downloaded." - # renpy/common/00updater.rpy:1102 + # renpy/common/00updater.rpy:1385 old "The update file does not have the correct digest - it may have been corrupted." new "The update file does not have the correct digest - it may have been corrupted." - # renpy/common/00updater.rpy:1252 + # renpy/common/00updater.rpy:1535 old "While unpacking {}, unknown type {}." new "While unpacking {}, unknown type {}." - # renpy/common/00updater.rpy:1624 + # renpy/common/00updater.rpy:2015 old "Updater" new "Updater" - # renpy/common/00updater.rpy:1631 + # renpy/common/00updater.rpy:2022 old "An error has occured:" new "An error has occured:" - # renpy/common/00updater.rpy:1633 + # renpy/common/00updater.rpy:2024 old "Checking for updates." new "Checking for updates." - # renpy/common/00updater.rpy:1635 + # renpy/common/00updater.rpy:2026 old "This program is up to date." new "This program is up to date." - # renpy/common/00updater.rpy:1637 + # renpy/common/00updater.rpy:2028 old "[u.version] is available. Do you want to install it?" new "[u.version] is available. Do you want to install it?" - # renpy/common/00updater.rpy:1639 + # renpy/common/00updater.rpy:2030 old "Preparing to download the updates." new "Preparing to download the updates." - # renpy/common/00updater.rpy:1641 + # renpy/common/00updater.rpy:2032 old "Downloading the updates." new "Downloading the updates." - # renpy/common/00updater.rpy:1643 + # renpy/common/00updater.rpy:2034 old "Unpacking the updates." new "Unpacking the updates." - # renpy/common/00updater.rpy:1645 + # renpy/common/00updater.rpy:2036 old "Finishing up." new "Finishing up." - # renpy/common/00updater.rpy:1647 + # renpy/common/00updater.rpy:2038 old "The updates have been installed. The program will restart." new "The updates have been installed. The program will restart." - # renpy/common/00updater.rpy:1649 + # renpy/common/00updater.rpy:2040 old "The updates have been installed." new "The updates have been installed." - # renpy/common/00updater.rpy:1651 + # renpy/common/00updater.rpy:2042 old "The updates were cancelled." new "The updates were cancelled." - # renpy/common/00updater.rpy:1666 + # renpy/common/00updater.rpy:2057 old "Proceed" new "Proceed" - # renpy/common/00compat.rpy:364 + # renpy/common/00updater.rpy:2072 + old "Preparing to download the game data." + new "Preparing to download the game data." + + # renpy/common/00updater.rpy:2074 + old "Downloading the game data." + new "Downloading the game data." + + # renpy/common/00updater.rpy:2076 + old "The game data has been downloaded." + new "The game data has been downloaded." + + # renpy/common/00updater.rpy:2078 + old "An error occured when trying to download game data:" + new "An error occured when trying to download game data:" + + # renpy/common/00updater.rpy:2083 + old "This game cannot be run until the game data has been downloaded." + new "This game cannot be run until the game data has been downloaded." + + # renpy/common/00updater.rpy:2090 + old "Retry" + new "Retry" + + # renpy/common/00compat.rpy:421 old "Fullscreen" new "Fullscreen" @@ -1113,159 +1161,343 @@ translate None strings: old "Back (B)" new "Back (B)" - # renpy/common/_errorhandling.rpym:555 + # renpy/common/_errorhandling.rpym:662 old "Open" new "Open" - # renpy/common/_errorhandling.rpym:557 + # renpy/common/_errorhandling.rpym:664 old "Opens the traceback.txt file in a text editor." new "Opens the traceback.txt file in a text editor." - # renpy/common/_errorhandling.rpym:559 + # renpy/common/_errorhandling.rpym:666 old "Copy BBCode" new "Copy BBCode" - # renpy/common/_errorhandling.rpym:561 + # renpy/common/_errorhandling.rpym:668 old "Copies the traceback.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/." new "Copies the traceback.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/." - # renpy/common/_errorhandling.rpym:563 + # renpy/common/_errorhandling.rpym:670 old "Copy Markdown" new "Copy Markdown" - # renpy/common/_errorhandling.rpym:565 + # renpy/common/_errorhandling.rpym:672 old "Copies the traceback.txt file to the clipboard as Markdown for Discord." new "Copies the traceback.txt file to the clipboard as Markdown for Discord." - # renpy/common/_errorhandling.rpym:594 + # renpy/common/_errorhandling.rpym:703 old "An exception has occurred." new "An exception has occurred." - # renpy/common/_errorhandling.rpym:617 + # renpy/common/_errorhandling.rpym:726 old "Rollback" new "Rollback" - # renpy/common/_errorhandling.rpym:619 + # renpy/common/_errorhandling.rpym:728 old "Attempts a roll back to a prior time, allowing you to save or choose a different choice." new "Attempts a roll back to a prior time, allowing you to save or choose a different choice." - # renpy/common/_errorhandling.rpym:622 + # renpy/common/_errorhandling.rpym:731 old "Ignore" new "Ignore" - # renpy/common/_errorhandling.rpym:626 + # renpy/common/_errorhandling.rpym:735 old "Ignores the exception, allowing you to continue." new "Ignores the exception, allowing you to continue." - # renpy/common/_errorhandling.rpym:628 + # renpy/common/_errorhandling.rpym:737 old "Ignores the exception, allowing you to continue. This often leads to additional errors." new "Ignores the exception, allowing you to continue. This often leads to additional errors." - # renpy/common/_errorhandling.rpym:632 + # renpy/common/_errorhandling.rpym:741 old "Reload" new "Reload" - # renpy/common/_errorhandling.rpym:634 + # renpy/common/_errorhandling.rpym:743 old "Reloads the game from disk, saving and restoring game state if possible." new "Reloads the game from disk, saving and restoring game state if possible." - # renpy/common/_errorhandling.rpym:637 + # renpy/common/_errorhandling.rpym:746 old "Console" new "Console" - # renpy/common/_errorhandling.rpym:639 + # renpy/common/_errorhandling.rpym:748 old "Opens a console to allow debugging the problem." new "Opens a console to allow debugging the problem." - # renpy/common/_errorhandling.rpym:652 + # renpy/common/_errorhandling.rpym:761 old "Quits the game." new "Quits the game." - # renpy/common/_errorhandling.rpym:673 + # renpy/common/_errorhandling.rpym:782 old "Parsing the script failed." new "Parsing the script failed." - # renpy/common/00console.rpy:524 + # renpy/common/_developer/developer.rpym:38 + old "Developer Menu" + new "Developer Menu" + + # renpy/common/_developer/developer.rpym:43 + old "Interactive Director (D)" + new "Interactive Director (D)" + + # renpy/common/_developer/developer.rpym:45 + old "Reload Game (Shift+R)" + new "Reload Game (Shift+R)" + + # renpy/common/_developer/developer.rpym:47 + old "Console (Shift+O)" + new "Console (Shift+O)" + + # renpy/common/_developer/developer.rpym:49 + old "Variable Viewer" + new "Variable Viewer" + + # renpy/common/_developer/developer.rpym:51 + old "Persistent Viewer" + new "Persistent Viewer" + + # renpy/common/_developer/developer.rpym:53 + old "Image Location Picker" + new "Image Location Picker" + + # renpy/common/_developer/developer.rpym:55 + old "Filename List" + new "Filename List" + + # renpy/common/_developer/developer.rpym:59 + old "Show Image Load Log (F4)" + new "Show Image Load Log (F4)" + + # renpy/common/_developer/developer.rpym:62 + old "Hide Image Load Log (F4)" + new "Hide Image Load Log (F4)" + + # renpy/common/_developer/developer.rpym:65 + old "Image Attributes" + new "Image Attributes" + + # renpy/common/_developer/developer.rpym:69 + old "Show Translation Identifiers" + new "Show Translation Identifiers" + + # renpy/common/_developer/developer.rpym:72 + old "Hide Translation Identifiers" + new "Hide Translation Identifiers" + + # renpy/common/_developer/developer.rpym:77 + old "Speech Bubble Editor (Shift+B)" + new "Speech Bubble Editor (Shift+B)" + + # renpy/common/_developer/developer.rpym:81 + old "Show Filename and Line" + new "Show Filename and Line" + + # renpy/common/_developer/developer.rpym:84 + old "Hide Filename and Line" + new "Hide Filename and Line" + + # renpy/common/_developer/developer.rpym:127 + old "Layer [l]:" + new "Layer [l]:" + + # renpy/common/_developer/developer.rpym:131 + old " [name] [attributes] (hidden)" + new " [name] [attributes] (hidden)" + + # renpy/common/_developer/developer.rpym:135 + old " [name] [attributes]" + new " [name] [attributes]" + + # renpy/common/_developer/developer.rpym:187 + old "Nothing to inspect." + new "Nothing to inspect." + + # renpy/common/_developer/developer.rpym:198 + old "Hide deleted" + new "Hide deleted" + + # renpy/common/_developer/developer.rpym:198 + old "Show deleted" + new "Show deleted" + + # renpy/common/_developer/developer.rpym:349 + old "Rectangle copied to clipboard." + new "Rectangle copied to clipboard." + + # renpy/common/_developer/developer.rpym:352 + old "Position copied to clipboard." + new "Position copied to clipboard." + + # renpy/common/_developer/developer.rpym:364 + old "Rectangle: %r" + new "Rectangle: %r" + + # renpy/common/_developer/developer.rpym:367 + old "Mouse position: %r" + new "Mouse position: %r" + + # renpy/common/_developer/developer.rpym:372 + old "Right-click or escape to quit." + new "Right-click or escape to quit." + + # renpy/common/_developer/developer.rpym:420 + old "Type to filter: " + new "Type to filter: " + + # renpy/common/_developer/developer.rpym:538 + old "Textures: [tex_count] ([tex_size_mb:.1f] MB)" + new "Textures: [tex_count] ([tex_size_mb:.1f] MB)" + + # renpy/common/_developer/developer.rpym:542 + old "Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)" + new "Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)" + + # renpy/common/_developer/developer.rpym:552 + old "✔ " + new "✔ " + + # renpy/common/_developer/developer.rpym:555 + old "✘ " + new "✘ " + + # renpy/common/_developer/developer.rpym:560 + old "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}" + new "\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}" + + # renpy/common/_developer/developer.rpym:606 + old "\n{color=#fff}Copied to clipboard.{/color}" + new "\n{color=#fff}Copied to clipboard.{/color}" + + # renpy/common/_developer/developer.rpym:612 + old "\n{color=#fff}Click to copy.\nDrag to move.{/color}" + new "\n{color=#fff}Click to copy.\nDrag to move.{/color}" + + # renpy/common/_developer/developer.rpym:657 + old "Click to open in editor." + new "Click to open in editor." + + # renpy/common/_developer/inspector.rpym:38 + old "Displayable Inspector" + new "Displayable Inspector" + + # renpy/common/_developer/inspector.rpym:61 + old "Size" + new "Size" + + # renpy/common/_developer/inspector.rpym:65 + old "Style" + new "Style" + + # renpy/common/_developer/inspector.rpym:71 + old "Location" + new "Location" + + # renpy/common/_developer/inspector.rpym:122 + old "Inspecting Styles of [displayable_name!q]" + new "Inspecting Styles of [displayable_name!q]" + + # renpy/common/_developer/inspector.rpym:139 + old "displayable:" + new "displayable:" + + # renpy/common/_developer/inspector.rpym:145 + old " (no properties affect the displayable)" + new " (no properties affect the displayable)" + + # renpy/common/_developer/inspector.rpym:147 + old " (default properties omitted)" + new " (default properties omitted)" + + # renpy/common/_developer/inspector.rpym:185 + old "" + new "" + + # renpy/common/00console.rpy:537 old "Press to exit console. Type help for help.\n" new "Press to exit console. Type help for help.\n" - # renpy/common/00console.rpy:528 + # renpy/common/00console.rpy:541 old "Ren'Py script enabled." new "Ren'Py script enabled." - # renpy/common/00console.rpy:530 + # renpy/common/00console.rpy:543 old "Ren'Py script disabled." new "Ren'Py script disabled." - # renpy/common/00console.rpy:779 - old "help: show this help" - new "help: show this help" + # renpy/common/00console.rpy:793 + old "help: show this help\n help : show signature and documentation of " + new "help: show this help\n help : show signature and documentation of " - # renpy/common/00console.rpy:784 + # renpy/common/00console.rpy:817 + old "Help may display undocumented functions. Please check that the function or\nclass you want to use is documented.\n\n" + new "Help may display undocumented functions. Please check that the function or\nclass you want to use is documented.\n\n" + + # renpy/common/00console.rpy:826 old "commands:\n" new "commands:\n" - # renpy/common/00console.rpy:794 + # renpy/common/00console.rpy:836 old " : run the statement\n" new " : run the statement\n" - # renpy/common/00console.rpy:796 + # renpy/common/00console.rpy:838 old " : run the expression or statement" new " : run the expression or statement" - # renpy/common/00console.rpy:804 + # renpy/common/00console.rpy:846 old "clear: clear the console history" new "clear: clear the console history" - # renpy/common/00console.rpy:808 + # renpy/common/00console.rpy:850 old "exit: exit the console" new "exit: exit the console" - # renpy/common/00console.rpy:816 + # renpy/common/00console.rpy:858 old "stack: print the return stack" new "stack: print the return stack" - # renpy/common/00console.rpy:838 + # renpy/common/00console.rpy:880 old "load : loads the game from slot" new "load : loads the game from slot" - # renpy/common/00console.rpy:851 + # renpy/common/00console.rpy:893 old "save : saves the game in slot" new "save : saves the game in slot" - # renpy/common/00console.rpy:862 + # renpy/common/00console.rpy:904 old "reload: reloads the game, refreshing the scripts" new "reload: reloads the game, refreshing the scripts" - # renpy/common/00console.rpy:870 + # renpy/common/00console.rpy:912 old "watch : watch a python expression\n watch short: makes the representation of traced expressions short (default)\n watch long: makes the representation of traced expressions as is" new "watch : watch a python expression\n watch short: makes the representation of traced expressions short (default)\n watch long: makes the representation of traced expressions as is" - # renpy/common/00console.rpy:907 + # renpy/common/00console.rpy:949 old "unwatch : stop watching an expression" new "unwatch : stop watching an expression" - # renpy/common/00console.rpy:953 + # renpy/common/00console.rpy:995 old "unwatchall: stop watching all expressions" new "unwatchall: stop watching all expressions" - # renpy/common/00console.rpy:974 + # renpy/common/00console.rpy:1016 old "jump