Compare commits

...

58 Commits

Author SHA1 Message Date
minjaesong
453459e3b6 fix: some UIs won't fade in/out on open/close 2023-07-03 21:35:46 +09:00
minjaesong
bad72dd353 simple SAVING ui for teleportation 2023-07-03 20:26:30 +09:00
minjaesong
13185f0565 preliminary gui thing for teleportation 2023-07-03 17:46:57 +09:00
minjaesong
fcaf4c97f1 seemingly working world teleporter (no saving... ui tho) 2023-07-03 00:10:46 +09:00
minjaesong
9c396e7b8d new world via teleporter wip 2023-07-02 18:54:04 +09:00
minjaesong
afb7dff5d2 some comment elaboration 2023-07-02 01:25:34 +09:00
minjaesong
5d0514040c lang split into two files 2023-06-30 16:20:57 +09:00
minjaesong
7c1806946b worldportal: showing tooltip to tell why the button is disabled 2023-06-30 03:07:45 +09:00
minjaesong
e5e02681b8 weather only change on titlescreen 2023-06-30 00:53:46 +09:00
minjaesong
6db3baf691 clearing up interpolation functions 2023-06-30 00:14:28 +09:00
minjaesong
07cbcbe79b better title screen camera smoothing 2023-06-29 22:50:44 +09:00
minjaesong
57a9f7febc graph guidance colour scheme change; added easter egg where the camera might pan towards left 2023-06-29 02:56:33 +09:00
minjaesong
16cfaaea27 titlescreen follows the terrain better 2023-06-28 22:49:45 +09:00
minjaesong
72c742897e fix: load menu buttons are pushed when they should not listen to the touchdown event 2023-06-28 17:47:04 +09:00
minjaesong
23af64deb4 proper savegame backups sorting 2023-06-28 16:42:38 +09:00
minjaesong
bb017fa9b7 gui for load savegame 2023-06-28 16:10:15 +09:00
minjaesong
1745bb16db save deletion works but gui is still wip 2023-06-28 11:05:28 +09:00
minjaesong
370583d1af actual red button for DELETE 2023-06-28 00:55:36 +09:00
minjaesong
66b651c627 delete character file gui wip 2023-06-27 22:46:16 +09:00
minjaesong
c5874a7f3d finally working again: create new character
todo: make delete character work
2023-06-27 21:13:51 +09:00
minjaesong
057905c3b7 thumb generation for player saves 2023-06-27 01:21:05 +09:00
minjaesong
2b50562002 save juggling for autosaves 2023-06-26 23:10:52 +09:00
minjaesong
73a8198378 fix: loading a game would load the oldest backup save 2023-06-26 21:43:25 +09:00
minjaesong
1ef479124e actually working load manual/auto button 2023-06-26 20:18:00 +09:00
minjaesong
e5e8028b3f fix: clickOnceListener would not fired if screen is magnified 2023-06-26 19:07:25 +09:00
minjaesong
739b51af95 manual/auto selection for savegame loading 2023-06-26 18:18:59 +09:00
minjaesong
f9f49ab63c new savegame loader is not quite working yet 2023-06-26 01:09:47 +09:00
minjaesong
a497463349 some ui updates 2023-06-25 20:46:52 +09:00
minjaesong
253db56c4f the baloon now has opacity control 2023-06-25 13:58:37 +09:00
minjaesong
3d13941060 new savegame loading wip 2023-06-24 23:44:48 +09:00
minjaesong
592e489411 warning for apple rosetta 2023-06-24 02:06:22 +09:00
minjaesong
49b2011ea0 a little bit generalised titlescreen warning printing 2023-06-24 01:12:43 +09:00
minjaesong
61e6255b52 some warning for apple rosetta 2023-06-24 01:03:58 +09:00
minjaesong
2e956f89f5 fix for edge case where 64-bit x86 CPU not reporting itself as AMD64 2023-06-24 00:40:59 +09:00
minjaesong
e8ffd1f844 proper bootstrap codes 2023-06-23 18:44:05 +09:00
minjaesong
0882145f9c some autosave stuffs; bootloader to actually use bundled runtime 2023-06-23 17:29:49 +09:00
minjaesong
28e2179e44 don't xstartonfirstthread the bootstrapper 2023-06-23 12:11:19 +09:00
minjaesong
48eb1ffd8f printout child proc's out and err to console 2023-06-22 23:11:34 +09:00
minjaesong
6daccb2e62 locales 2023-06-22 22:27:56 +09:00
minjaesong
8c9d5a26fb more code trimming 2023-06-22 22:05:10 +09:00
minjaesong
ee3e5b14cd rm unused code snippet 2023-06-22 21:21:09 +09:00
minjaesong
5c39df9080 bootstrapper for the App so that the user can change the max heap in-game 2023-06-22 21:08:09 +09:00
minjaesong
5d77694316 windows build script now produces .exe 2023-06-21 23:49:42 +09:00
minjaesong
cf111d2507 world portal writing current world to actorvalue 'worldportaldict' if it's not there 2023-06-20 13:45:32 +09:00
minjaesong
724ace3f00 for now ui simply closes on teleport target selection 2023-06-20 13:22:14 +09:00
minjaesong
1457cbffb3 worldportal: submitting teleportrequest works, needs UI refinement 2023-06-20 00:15:53 +09:00
minjaesong
7a42066392 electric: rising/falling edge and level detection 2023-06-19 18:42:08 +09:00
minjaesong
528b975350 wiresim: signal sinking actors are only getting updated when the sim calls for 2023-06-19 16:34:39 +09:00
minjaesong
9e9064dd55 world portal: world search is now new world 2023-06-19 00:50:55 +09:00
minjaesong
138c6d22d2 some font stuffs for ui 2023-06-18 21:40:06 +09:00
minjaesong
a33f0e7ab4 world search ui integrated to world portal ui 2023-06-18 21:29:18 +09:00
minjaesong
93c427473d inventory backdrop is now image 2023-06-18 16:02:25 +09:00
minjaesong
6b8798a19e single screen ui for world portal 2023-06-18 01:28:51 +09:00
minjaesong
376595d7cd fix: scroll controller for portal listing is 2 px shorter that it should 2023-06-18 00:40:55 +09:00
minjaesong
4cc52b5585 fix: storage chest ui would be shifted to left and any mouse button would trigger the action 2023-06-17 23:19:09 +09:00
minjaesong
0ff71f39fe list scroll for portallisting 2023-06-17 17:10:13 +09:00
minjaesong
13f487a562 inventory navbar to its own uiitem 2023-06-17 16:46:15 +09:00
minjaesong
0599ce91b1 reflection function update 2023-06-12 15:08:42 +09:00
105 changed files with 4093 additions and 1062 deletions

View File

@@ -13,6 +13,10 @@
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-controllers-desktop-2.2.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/GetCpuName.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jxinput-1.0.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/commons-csv-1.8.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/prtree.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/Terrarum_Joise.jar" path-in-jar="/" />
@@ -72,21 +76,17 @@
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-platform-1.11.0-natives-armeabi-v7a.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-platform-1.11.0-natives-desktop.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-platform-1.11.0-natives-x86_64.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.21/kotlin-stdlib-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.21/kotlin-stdlib-common-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-test/1.8.21/kotlin-test-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.21/kotlin-reflect-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/graal-sdk-22.3.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-71.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-22.3.1-edit.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.21/kotlin-stdlib-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.21/kotlin-stdlib-common-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-test/1.8.21/kotlin-test-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-22.3.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-22.3.1-edit.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-22.3.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-71.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-22.3.1-edit.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-22.3.1-edit.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/graal-sdk-22.3.1.jar" path-in-jar="/" />
</root>
</artifact>
</component>

19
.idea/runConfigurations/Principii.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Principii" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="net.torvald.terrarum.Principii" />
<module name="TerrarumBuild" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="net.torvald.terrarum.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="BuildArtifacts" enabled="true">
<artifact name="ModuleComputers" />
<artifact name="TerrarumBuild" />
</option>
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -195,17 +195,10 @@ internal class UIHomeComputer : UICanvas(
}
override fun doOpening(delta: Float) {
super.doOpening(delta)
fixture.startVM()
}
override fun doClosing(delta: Float) {
}
override fun endOpening(delta: Float) {
}
override fun endClosing(delta: Float) {
}
override fun dispose() {
fbo.dispose()

Binary file not shown.

View File

@@ -7,12 +7,10 @@
"MENU_LABEL_PRESS_START_SYMBOL": "Press >",
"MENU_MODULES" : "Modules",
"MENU_CREDIT_GPL_DNT" : "GPL",
"MENU_LABEL_JVM_DNT" : "JVM",
"GAME_ACTION_MOVE_VERB" : "Move",
"GAME_ACTION_ZOOM" : "Zoom",
"MENU_LABEL_RESET" : "Reset",
"GAME_32BIT_WARNING1": "It looks like youre running a 32-Bit version of Java.",
"GAME_32BIT_WARNING2": "Please download and install the latest 64-Bit Java at:",
"GAME_32BIT_WARNING3": "https://www.java.com/en/download/",
"MENU_OPTION_STREAMERS_LAYOUT": "Chat Overlay",
"MENU_LABEL_RESTART_REQUIRED": "Restart Required",
"MENU_LABEL_KEYBOARD_LAYOUT": "Keyboard Layout",
@@ -21,11 +19,19 @@
"MENU_OPTIONS_BLUR": "Blur",
"MENU_OPTIONS_PARTICLES": "Particles",
"MENU_IO_IMPORT": "Import",
"APP_NOMODULE_1": "No Module is currently loaded.",
"APP_NOMODULE_2": "Please configure your Load Order and restart:",
"MENU_LABEL_KEYCONFIG_HELP1": "Click On the Keycap to Assign Actions",
"MENU_LABEL_IME_TOGGLE": "Toggle IME",
"MENU_LABEL_PASTE_FROM_CLIPBOARD": "Paste from Cliboard",
"MENU_LABEL_PASTE_FROM_CLIPBOARD": "Paste from Clipboard",
"MENU_OPTIONS_PERFORMANCE": "Performance",
"MENU_LABEL_DELETE": "Delete"
"MENU_LABEL_DELETE": "Delete",
"MENU_OPTIONS_JVM_HEAP_MAX": "Max Heap Memory",
"MENU_OPTIONS_AUTOSAVE": "Autosave",
"CONTEXT_TIME_MINUTE_PLURAL": "Minutes",
"CONTEXT_TIME_SECOND_PLURAL": "Seconds",
"MENU_LABEL_SYSTEM_INFO": "System Info",
"MENU_OPTIONS_NOTIFICATION_DISPLAY_DURATION": "Show notification for",
"MENU_LABEL_STREAMING": "Livestreaming",
"MENU_LABEL_EXTRA_JVM_ARGUMENTS": "Extra Arguments",
"MENU_IO_MANUAL_SAVE": "Manual Save",
"MENU_IO_AUTOSAVE": "Autosave",
"MENU_OPTIONS_DEBUG_CONSOLE": "Debug Console"
}

View File

@@ -0,0 +1,16 @@
{
"GAME_32BIT_WARNING1": "32비트 버전의 Java를 사용중인 것 같습니다.",
"GAME_32BIT_WARNING2": "아래 링크에서 최신 64비트 Java를 내려받아 설치해주세요.",
"GAME_32BIT_WARNING3": "https://www.java.com/ko/download/",
"GAME_APPLE_ROSETTA_WARNING1": "Apple Silicon이 탑재된 Mac을 사용 중이지만 x86 빌드의 게임을 실행 중입니다.",
"GAME_APPLE_ROSETTA_WARNING2": "최적의 성능과 게임 경험을 위해 Apple Silicon용 빌드의 게임을 이용해 주십시오.",
"APP_NOMODULE_1": "현재 불러와진 모듈이 없습니다.",
"APP_NOMODULE_2": "다음의 파일에서 불러오기 순서를 설정하고 게임을 재시작하십시오.",
"MENU_LABEL_KEYCONFIG_HELP1": "키캡을 클릭해 컨트롤을 배정하십시오",
"GAME_PREV_SAVE_WAS_LOADED1": "가장 최근에 저장된 게임이 손상되었습니다.",
"GAME_PREV_SAVE_WAS_LOADED2": "이전에 저장된 게임을 불러왔습니다.",
"GAME_MORE_RECENT_AUTOSAVE1": "자동 저장된 게임이 수동으로 저장한 게임보다 더 최신입니다.",
"GAME_MORE_RECENT_AUTOSAVE2": "불러올 게임을 선택해 주십시오.",
"MENU_LABEL_SAVE_WILL_BE_DELETED": "선택된 세이브가 삭제됩니다.",
"MENU_LABEL_UNSAVED_PROGRESSES_WILL_BE_LOST": "저장하지 않은 변동사항을 잃게 됩니다."
}

View File

@@ -2335,7 +2335,7 @@
},
{
"n": "MENU_OPTIONS",
"s": "Stillingar "
"s": "Valkostir "
},
{
"n": "MENU_OPTIONS_ADVANCEDGRAPHICS",
@@ -2395,11 +2395,11 @@
},
{
"n": "MENU_OPTIONS_GAMEPLAY",
"s": "Gameplay Options"
"s": "Leikvalkostir"
},
{
"n": "MENU_OPTIONS_GRAPHICS",
"s": "Grafíkstillingar"
"s": "Grafíkvalkostir"
},
{
"n": "MENU_OPTIONS_HUD",

View File

@@ -15,5 +15,15 @@
"MENU_OPTIONS_DITHER": "Dither",
"MENU_OPTIONS_BLUR": "Óskýrt",
"MENU_OPTIONS_PARTICLES": "Eind",
"MENU_IO_IMPORT": "Flytja inn"
"MENU_IO_IMPORT": "Flytja inn",
"APP_NOMODULE_1": "Engin eining er hlaðin eins og er.",
"APP_NOMODULE_2": "Vinsamlega stilltu hleðslupöntunina þína og endurræstu:",
"MENU_LABEL_KEYCONFIG_HELP1": "Smelltu á lyklalokið til að úthluta aðgerðum",
"MENU_LABEL_IME_TOGGLE": "Breyttu IME",
"MENU_LABEL_PASTE_FROM_CLIPBOARD": "Límdu frá klemmuspjald",
"MENU_OPTIONS_PERFORMANCE": "Afköst",
"MENU_LABEL_DELETE": "Eyða",
"MENU_OPTIONS_JVM_HEAP_MAX": "Hámarks hrúguminni",
"MENU_OPTIONS_AUTOSAVE": "Sjálfvirk vistun",
"CONTEXT_TIME_MINUTE_PLURAL": "Mínútur"
}

View File

@@ -6,12 +6,11 @@
"APP_WARNING_HEALTH_AND_SAFETY": "경고—건강과 안전을 위하여",
"MENU_LABEL_PRESS_START_SYMBOL": ">을 누르세요",
"MENU_MODULES" : "모듈",
"GAME_ACTION_MOVE_VERB" : "이동하기",
"GAME_ACTION_ZOOM" : "확대·축소",
"MENU_LABEL_RESET" : "재설정",
"GAME_32BIT_WARNING1": "32비트 버전의 Java를 사용중인 것 같습니다.",
"GAME_32BIT_WARNING2": "아래 링크에서 최신 64비트 Java를 내려받아 설치해주세요.",
"GAME_32BIT_WARNING3": "https://www.java.com/ko/download/",
"MENU_OPTION_STREAMERS_LAYOUT": "채팅창 오버레이",
"MENU_LABEL_RESTART_REQUIRED": "재시작 필요",
"MENU_LABEL_KEYBOARD_LAYOUT": "자판 배열",
@@ -20,10 +19,19 @@
"MENU_OPTIONS_BLUR": "흐림",
"MENU_OPTIONS_PARTICLES": "입자 수",
"MENU_IO_IMPORT": "가져오기",
"APP_NOMODULE_1": "현재 불러와진 모듈이 없습니다.",
"APP_NOMODULE_2": "다음의 파일에서 불러오기 순서를 설정하고 게임을 재시작하십시오.",
"MENU_LABEL_KEYCONFIG_HELP1": "키캡을 클릭해 컨트롤을 배정하십시오",
"MENU_LABEL_IME_TOGGLE": "입력기 켜고 끄기",
"MENU_LABEL_PASTE_FROM_CLIPBOARD": "복사한 텍스트 붙여넣기",
"MENU_OPTIONS_PERFORMANCE": "성능"
"MENU_OPTIONS_PERFORMANCE": "성능",
"MENU_LABEL_DELETE": "삭제",
"MENU_OPTIONS_JVM_HEAP_MAX": "최대 힙 메모리",
"MENU_OPTIONS_AUTOSAVE": "자동 저장",
"CONTEXT_TIME_MINUTE_PLURAL": "분",
"CONTEXT_TIME_SECOND_PLURAL": "초",
"MENU_LABEL_SYSTEM_INFO": "시스템 정보",
"MENU_OPTIONS_NOTIFICATION_DISPLAY_DURATION": "알림 표시 시간",
"MENU_LABEL_STREAMING": "실시간 방송",
"MENU_LABEL_EXTRA_JVM_ARGUMENTS": "추가 명령 인수",
"MENU_IO_MANUAL_SAVE": "수동 저장",
"MENU_IO_AUTOSAVE": "자동 저장",
"MENU_OPTIONS_DEBUG_CONSOLE": "디버그 콘솔"
}

View File

@@ -0,0 +1,15 @@
{
"GAME_32BIT_WARNING1": "32비트 버전의 Java를 사용중인 것 같습니다.",
"GAME_32BIT_WARNING2": "아래 링크에서 최신 64비트 Java를 내려받아 설치해주세요.",
"GAME_32BIT_WARNING3": "https://www.java.com/ko/download/",
"GAME_APPLE_ROSETTA_WARNING1": "Apple Silicon이 탑재된 Mac을 사용 중이지만 x86 빌드의 게임을 실행 중입니다.",
"GAME_APPLE_ROSETTA_WARNING2": "최적의 성능과 게임 경험을 위해 Apple Silicon용 빌드의 게임을 이용해 주십시오.",
"APP_NOMODULE_1": "현재 불러와진 모듈이 없습니다.",
"APP_NOMODULE_2": "다음의 파일에서 불러오기 순서를 설정하고 게임을 재시작하십시오.",
"MENU_LABEL_KEYCONFIG_HELP1": "키캡을 클릭해 컨트롤을 배정하십시오",
"GAME_PREV_SAVE_WAS_LOADED1": "가장 최근에 저장된 게임이 손상되었습니다.",
"GAME_PREV_SAVE_WAS_LOADED2": "이전에 저장된 게임을 불러왔습니다.",
"GAME_MORE_RECENT_AUTOSAVE1": "자동 저장된 게임이 수동으로 저장한 게임보다 더 최신입니다.",
"GAME_MORE_RECENT_AUTOSAVE2": "불러올 게임을 선택해 주십시오.",
"MENU_LABEL_SAVE_WILL_BE_DELETED": "선택된 세이브가 삭제됩니다."
}

Binary file not shown.

View File

@@ -17,7 +17,7 @@
"GAME_ACTION_CRAFT": "Craft",
"GAME_CRAFTING": "Crafting",
"GAME_CRAFTABLE_ITEMS": "Craftable Items",
"CONTEXT_WORLD_SEARCH": "World Search",
"CONTEXT_WORLD_LIST": "Worlds List",
"MENU_LABEL_RENAME": "Rename"
"MENU_LABEL_RENAME": "Rename",
"GAME_ACTION_TELEPORT": "Teleport",
"CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "This is a world currently playing."
}

View File

@@ -17,5 +17,8 @@
"GAME_ACTION_QUICKSEL": "빠른 선택",
"GAME_ACTION_CRAFT": "제작하기",
"GAME_CRAFTING": "제작",
"GAME_CRAFTABLE_ITEMS": "제작 가능한 아이템"
"GAME_CRAFTABLE_ITEMS": "제작 가능한 아이템",
"MENU_LABEL_RENAME": "이름 바꾸기",
"GAME_ACTION_TELEPORT": "텔레포트하기",
"CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "현재 플레이 중인 월드입니다."
}

View File

@@ -23,12 +23,13 @@ cp $SRCFILES/AppRun $DESTDIR/AppRun
chmod +x $DESTDIR/AppRun
# Copy over a Java runtime
cp -r "../out/$RUNTIME" $DESTDIR/
mkdir $DESTDIR/out
cp -r "../out/$RUNTIME" $DESTDIR/out/
# Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/
mv $DESTDIR/assets_release $DESTDIR/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/assets/
cp "../out/TerrarumBuild.jar" $DESTDIR/out/
# Pack everything to AppImage
ARCH=arm_aarch64 "./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; }

View File

@@ -23,12 +23,13 @@ cp $SRCFILES/AppRun $DESTDIR/AppRun
chmod +x $DESTDIR/AppRun
# Copy over a Java runtime
cp -r "../out/$RUNTIME" $DESTDIR/
mkdir $DESTDIR/out
cp -r "../out/$RUNTIME" $DESTDIR/out/
# Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/
mv $DESTDIR/assets_release $DESTDIR/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/assets/
cp "../out/TerrarumBuild.jar" $DESTDIR/out/
# Pack everything to AppImage
"./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; }

View File

@@ -25,11 +25,12 @@ cp $SRCFILES/Terrarum.sh $DESTDIR/Contents/MacOS/
chmod +x $DESTDIR/Contents/MacOS/Terrarum.sh
# Copy over a Java runtime
cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/
mkdir $DESTDIR/Contents/MacOS/out
cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/out/
# Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/Contents/MacOS/
mv $DESTDIR/Contents/MacOS/assets_release $DESTDIR/Contents/MacOS/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/Contents/MacOS/assets/
cp "../out/TerrarumBuild.jar" $DESTDIR/Contents/MacOS/out/
echo "Build successful: $DESTDIR"

View File

@@ -25,11 +25,12 @@ cp $SRCFILES/Terrarum.sh $DESTDIR/Contents/MacOS/
chmod +x $DESTDIR/Contents/MacOS/Terrarum.sh
# Copy over a Java runtime
cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/
mkdir $DESTDIR/Contents/MacOS/out
cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/out/
# Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/Contents/MacOS/
mv $DESTDIR/Contents/MacOS/assets_release $DESTDIR/Contents/MacOS/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/Contents/MacOS/assets/
cp "../out/TerrarumBuild.jar" $DESTDIR/Contents/MacOS/out/
echo "Build successful: $DESTDIR"

View File

@@ -16,15 +16,21 @@ rm -rf $DESTDIR || true
mkdir $DESTDIR
# Prepare an application
cp $SRCFILES/Terrarum.bat $DESTDIR/
if ! command -v x86_64-w64-mingw32-gcc &> /dev/null
then
echo 'Mingw32 not found; please install mingw64-cross-gcc (or similar) to your system' >&2; exit 1;
fi
x86_64-w64-mingw32-gcc -o $DESTDIR/Terrarum.exe $SRCFILES/Terrarum.c || { echo 'Building EXE failed' >&2; exit 1; }
# Copy over a Java runtime
cp -r "../out/$RUNTIME" $DESTDIR/
mkdir $DESTDIR/out
cp -r "../out/$RUNTIME" $DESTDIR/out/
# Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/
mv $DESTDIR/assets_release $DESTDIR/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/assets/
cp "../out/TerrarumBuild.jar" $DESTDIR/out/
# Temporary solution: zip everything
zip -r -9 -l "out/TerrarumWindows.x86.zip" $DESTDIR

View File

@@ -1,3 +1,3 @@
#!/bin/bash
cd "${0%/*}"
./runtime-linux-arm/bin/java -Xms1G -Xmx6G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./assets/TerrarumBuild.jar
./out/runtime-linux-arm/bin/java -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./out/TerrarumBuild.jar

View File

@@ -1,3 +1,3 @@
#!/bin/bash
cd "${0%/*}"
./runtime-linux-x86/bin/java -Xms1G -Xmx6G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./assets/TerrarumBuild.jar
./out/runtime-linux-x86/bin/java -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./out/TerrarumBuild.jar

View File

@@ -1,3 +1,3 @@
#!/bin/bash
cd "${0%/*}"
./runtime-osx-arm/bin/java -XstartOnFirstThread -Xms1G -Xmx6G -jar ./assets/TerrarumBuild.jar
./out/runtime-osx-arm/bin/java -jar ./out/TerrarumBuild.jar

View File

@@ -1,3 +1,3 @@
#!/bin/bash
cd "${0%/*}"
./runtime-osx-x86/bin/java -XstartOnFirstThread -Xms1G -Xmx6G -jar ./assets/TerrarumBuild.jar
./out/runtime-osx-x86/bin/java -jar ./out/TerrarumBuild.jar

View File

@@ -1,2 +0,0 @@
cd /D "%~dp0"
.\runtime-windows-x86\bin\java -Xms1G -Xmx6G -jar .\assets\TerrarumBuild.jar

View File

@@ -0,0 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
int main() {
return system(".\\out\\runtime-windows-x86\\bin\\java -jar .\\out\\TerrarumBuild.jar");
}

View File

@@ -1,3 +1,3 @@
Manifest-Version: 1.0
Main-Class: net.torvald.terrarum.App
Main-Class: net.torvald.terrarum.Principii

View File

@@ -231,6 +231,10 @@ final public class FastMath {
return (float) (((c4 * u + c3) * u + c2) * u + c1);
}
public static float interpolateCatmullRom(float u, float p0, float p1, float p2, float p3) {
return interpolateCatmullRom(u, 0.5f, p0, p1, p2, p3);
}
/**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
* here is the interpolation matrix
* m = [ 0.0 1.0 0.0 0.0 ]
@@ -316,24 +320,25 @@ final public class FastMath {
public static float interpolateHermite(float scale, float p0, float p1, float p2, float p3) {
return interpolateHermite(scale, p0, p1, p2, p3, 1f, 0f);
}
public static float interpolateHermite(float scale, float p0, float p1, float p2, float p3, float tension, float bias) {
// return interpolateHermite(scale, p0, p1, p2, p3, 0f, 0f);
float mu2 = scale * scale;
float mu3 = mu2 * scale;
float biasTensionTerms = 0.5f;//(1f + bias) * (1f - tension) / 2f;
float m0 = (p1 - p0) * (1f + bias) * (1f - tension) / 2f;
m0 += (p2 - p1) * (1f + bias) * (1f - tension) / 2f;
float m1 = (p2 - p1) * (1f + bias) * (1f - tension) / 2f;
m1 += (p3 - p2) * (1f + bias) * (1f - tension) / 2f;
float m0 = (p1 - p0) * biasTensionTerms;
float mTemp = (p2 - p1) * biasTensionTerms;
m0 += mTemp;
float m1 = mTemp;
m1 += (p3 - p2) * biasTensionTerms;
float a0 = 2 * mu3 - 3 * mu2 + 1;
float a1 = mu3 - 2 * mu2 + scale;
float a2 = mu3 - mu2;
float a3 = -2 * mu3 + 3 * mu2;
float a0 = 2*mu3 - 3*mu2 + 1;
float a1 = 1*mu3 - 2*mu2 + scale;
float a2 = 1*mu3 - 1*mu2 + 0;
float a3 = -2*mu3 + 3*mu2 + 0;
return a0 * p1 + a1 * m0 + a2 * m1 + a3 * p2;
return a0*p1 + a1*m0 + a2*m1 + a3*p2;
}
//public static float interpolateHermite(float scale, float p0, float p1, float p2, float p3, float tension, float bias) {}
/**

View File

@@ -18,7 +18,7 @@ public class XXHash32 {
public static int hashGeoCoord(int x, int y) {
int p = ((x & 65535) << 16) | (y & 65535);
return hash(new byte[]{(byte) p, (byte)(p >>> 8), (byte)(p >>> 16), (byte)(p >>> 24)}, 10000);
return hash(new byte[]{(byte) p, (byte)(p >>> 8), (byte)(p >>> 16), (byte)(p >>> 24)}, x ^ y);
}
public static int hash(byte[] data, int seed) {

View File

@@ -3,15 +3,15 @@ package net.torvald.reflection
/**
* Created by minjaesong on 2023-03-25.
*/
fun Any.extortField(name: String): Any? { // yes I'm deliberately using negative words for the function name
inline fun <reified T> Any.extortField(name: String): T? { // yes I'm deliberately using negative words for the function name
return this.javaClass.getDeclaredField(name).let {
it.isAccessible = true
it.get(this)
it.get(this) as T?
}
}
fun Any.forceInvoke(name: String, params: Array<Any>): Any? { // yes I'm deliberately using negative words for the function name
inline fun <reified T> Any.forceInvoke(name: String, params: Array<Any>): T? { // yes I'm deliberately using negative words for the function name
return this.javaClass.getDeclaredMethod(name, *(params.map { it.javaClass }.toTypedArray())).let {
it.isAccessible = true
it.invoke(this, *params)
it.invoke(this, *params) as T?
}
}

View File

@@ -142,6 +142,12 @@ public class App implements ApplicationListener {
public static final int GLOBAL_FRAMERATE_LIMIT = 300;
private static String undesirableConditions;
public static String getUndesirableConditions() {
return undesirableConditions;
}
/**
* These languages won't distinguish regional differences (e.g. enUS and enUK, frFR and frCA)
*/
@@ -201,11 +207,11 @@ public class App implements ApplicationListener {
* Sorted by the lastplaytime, in reverse order (index 0 is the most recent game played)
*/
public static ArrayList<UUID> sortedSavegameWorlds = new ArrayList();
public static HashMap<UUID, DiskSkimmer> savegameWorlds = new HashMap<>(); // UNSORTED even with the TreeMap
public static HashMap<UUID, SavegameCollection> savegameWorlds = new HashMap<>(); // UNSORTED even with the TreeMap
public static HashMap<UUID, String> savegameWorldsName = new HashMap<>();
public static ArrayList<UUID> sortedPlayers = new ArrayList();
public static HashMap<UUID, DiskSkimmer> savegamePlayers = new HashMap<>();
public static HashMap<UUID, SavegameCollection> savegamePlayers = new HashMap<>();
public static HashMap<UUID, String> savegamePlayersName = new HashMap<>();
public static void updateListOfSavegames() {
@@ -346,10 +352,13 @@ public class App implements ApplicationListener {
processorVendor = "Unknown CPU";
}
if (processor.startsWith("Apple M") && Objects.equals(systemArch, "aarch64")) {
if (processor.startsWith("Apple M") && systemArch.equals("aarch64")) {
isAppleM = true;
System.out.println("Apple Proprietary "+processor+" detected; don't expect smooth sailing...");
}
if (processor.startsWith("Apple M") && !systemArch.equals("aarch64")) {
undesirableConditions = "apple_execution_through_rosetta";
}
if (!IS_DEVELOPMENT_BUILD) {
var p = UnsafeHelper.INSTANCE.allocate(64);
@@ -374,8 +383,8 @@ public class App implements ApplicationListener {
ShaderProgram.pedantic = false;
scr = new TerrarumScreenSize(getConfigInt("screenwidth"), getConfigInt("screenheight"));
int width = (int) Math.round(scr.getWidth() * scr.getMagn());
int height = (int) Math.round(scr.getHeight() * scr.getMagn());
int width = scr.getWindowW();
int height = scr.getWindowH();
Lwjgl3ApplicationConfiguration appConfig = new Lwjgl3ApplicationConfiguration();
//appConfig.useGL30 = false; // https://stackoverflow.com/questions/46753218/libgdx-should-i-use-gl30
@@ -557,7 +566,7 @@ public class App implements ApplicationListener {
false,
64, false, 0.5f, false
);
fontUITitle.setInterchar(2);
fontUITitle.setInterchar(1);
fontGameFBO = new TerrarumSansBitmap(FONT_DIR, false, true, false,
false,
64, false, 203f/255f, false
@@ -631,7 +640,6 @@ public class App implements ApplicationListener {
// process screenshot request
if (screenshotRequested) {
FrameBufferManager.begin(postProcessorOutFBO);
screenshotRequested = false;
try {
Pixmap p = Pixmap.createFromFrameBuffer(0, 0, scr.getWidth(), scr.getHeight());
PixmapIO.writePNG(Gdx.files.absolute(defaultDir+"/Screenshot-"+String.valueOf(System.currentTimeMillis())+".png"), p, 9, true);
@@ -643,6 +651,7 @@ public class App implements ApplicationListener {
Terrarum.INSTANCE.getIngame().sendNotification("Failed to take screenshot: "+e.getMessage());
}
FrameBufferManager.end();
screenshotRequested = false;
}
@@ -767,12 +776,9 @@ public class App implements ApplicationListener {
@Override
public void resize(int w0, int h0) {
int w = (w0%2==0)?w0:w0+1;
int h = (h0%2==0)?h0:h0+1;
float magn = (float) getConfigDouble("screenmagnifying");
int width = Math.round(w / magn);
int height = Math.round(h / magn);
int width = (int) Math.floor(w0 / magn);
int height = (int) Math.floor(h0 / magn);
printdbg(this, "Resize called: "+width+","+height);
@@ -782,7 +788,7 @@ public class App implements ApplicationListener {
//initViewPort(width, height);
scr.setDimension(width, height, magn, w, h);
scr.setDimension(width, height, magn);
if (currentScreen != null) currentScreen.resize(scr.getWidth(), scr.getHeight());
TerrarumPostProcessor.INSTANCE.resize(scr.getWidth(), scr.getHeight());
@@ -855,7 +861,7 @@ public class App implements ApplicationListener {
fullscreenQuad.dispose();
logoBatch.dispose();
batch.dispose();
shapeRender.dispose();
// shapeRender.dispose();
fontGame.dispose();
fontGameFBO.dispose();
@@ -1133,6 +1139,11 @@ public class App implements ApplicationListener {
public static RunningEnvironment environment;
/** defaultDir + "/Recycled/Players" */
public static String recycledPlayersDir;
/** defaultDir + "/Recycled/Worlds" */
public static String recycledWorldsDir;
private static void getDefaultDirectory() {
String OS = OSName.toUpperCase();
if (OS.contains("WIN")) {
@@ -1162,6 +1173,8 @@ public class App implements ApplicationListener {
worldsDir = defaultDir + "/Worlds";
configDir = defaultDir + "/config.json";
loadOrderDir = defaultDir + "/LoadOrder.txt";
recycledPlayersDir = defaultDir + "/Recycled/Players";
recycledWorldsDir = defaultDir + "/Recycled/Worlds";
System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem));
System.out.println(String.format("os.version = %s", OSVersion));
@@ -1171,10 +1184,12 @@ public class App implements ApplicationListener {
private static void createDirs() {
File[] dirs = {
new File(saveDir),
// new File(saveDir),
new File(saveSharedDir),
new File(playersDir),
new File(worldsDir)
new File(worldsDir),
new File(recycledPlayersDir),
new File(recycledWorldsDir),
};
for (File it : dirs) {

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum
import com.badlogic.gdx.Gdx
import net.torvald.unicode.BULLET
import net.torvald.unicode.ENDASH
@@ -223,6 +224,22 @@ Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
""").split('\n')
private val javaVersion = System.getProperty("java.version")
private val osName = App.OSName
private val osVersion = App.OSVersion
private val sysArch = App.systemArch
private val processor = App.processor
private val processorVendor = App.processorVendor
private val glinfo = Gdx.graphics.glVersion.debugVersionString
val systeminfo: List<String>; get() = """
JRE Version: $javaVersion
Operation System: $osName $osVersion
Architecture: $sysArch
Processor: $processor ($processorVendor)
GL Info: $glinfo
""".split('\n')
val gpl3: List<String>; get() = """ GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@@ -10,6 +10,9 @@ import com.badlogic.gdx.Input
object DefaultConfig {
val hashMap = hashMapOf<String, Any>(
"jvm_xmx" to 4,
"jvm_extra_cmd" to "",
"displayfps" to 0, // 0: no limit, non-zero: limit
"displayfpsidle" to 0, // 0: no limit, non-zero: limit
"displaycolourdepth" to 8,
@@ -19,9 +22,9 @@ object DefaultConfig {
"atlastexsize" to 2048,
"language" to App.getSysLang(),
"notificationshowuptime" to 4096, // 4s
"selecteditemnameshowuptime" to 4096, // 4s
"autosaveinterval" to 262144, // 4m22s
"notificationshowuptime" to 4000, // 4s
"selecteditemnameshowuptime" to 4000, // 4s
"autosaveinterval" to 300000, // 5s
"multithread" to true,
"showhealthmessageonstartup" to true,

View File

@@ -7,6 +7,7 @@ import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameactors.ActorID
import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.gameactors.ActorWithBody.Companion.PHYS_EPSILON_DIST
import net.torvald.terrarum.gameactors.BlockMarkerActor
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.gameitems.ItemID
@@ -26,6 +27,8 @@ import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.*
import java.util.concurrent.locks.Lock
import java.util.function.Consumer
@@ -49,8 +52,8 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
override fun getMax(axis: Int, t: ActorWithBody): Double =
when (axis) {
0 -> t.hitbox.endX
1 -> t.hitbox.endY
0 -> t.hitbox.endX - PHYS_EPSILON_DIST
1 -> t.hitbox.endY - PHYS_EPSILON_DIST
else -> throw IllegalArgumentException("nonexistent axis $axis for ${dimensions}-dimensional object")
}
}
@@ -406,6 +409,14 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
return uiTooltip.message
}
open fun requestForceSave(callback: () -> Unit) {
}
open fun saveTheGame(onSuccessful: () -> Unit, onError: (Throwable) -> Unit) {
}
/**
* Copies most recent `save` to `save.1`, leaving `save` for overwriting, previous `save.1` will be copied to `save.2`
*/
@@ -422,25 +433,55 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
// do not overwrite clean .2 with dirty .1
val flags3 = FileInputStream(file3).let { it.skip(49L); val r = it.read(); it.close(); r }
val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r }
if (!(flags3 == 0 && flags2 != 0) || !file3.exists()) file2.copyTo(file3, true)
if (!(flags3 == 0 && flags2 != 0) || !file3.exists()) Files.move(file2.toPath(), file3.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
} catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try {
// do not overwrite clean .2 with dirty .1
val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r }
val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r }
if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true)
if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) Files.move(file1.toPath(), file2.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
} catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try {
if (file2.exists() && !file3.exists())
file2.copyTo(file3, true)
Files.move(file2.toPath(), file3.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
if (file1.exists() && !file2.exists())
file1.copyTo(file2, true)
Files.move(file1.toPath(), file2.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
file.copyTo(file1, true)
} catch (e: IOException) {}
}
fun makeSavegameBackupCopyAuto(file0: File): File {
val file1 = File("${file0.absolutePath}.a")
val file2 = File("${file0.absolutePath}.b")
val file3 = File("${file0.absolutePath}.c")
try {
// do not overwrite clean .2 with dirty .1
val flags3 = FileInputStream(file3).let { it.skip(49L); val r = it.read(); it.close(); r }
val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r }
if (!(flags3 == 0 && flags2 != 0) || !file3.exists()) Files.move(file2.toPath(), file3.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
} catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try {
// do not overwrite clean .2 with dirty .1
val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r }
val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r }
if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) Files.move(file1.toPath(), file2.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
} catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try {
if (file2.exists() && !file3.exists())
Files.move(file2.toPath(), file3.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
if (file1.exists() && !file2.exists())
Files.move(file1.toPath(), file2.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
file0.copyTo(file1, true)
} catch (e: IOException) {}
return file1
}
// simple euclidean norm, squared
private val actorDistanceCalculator = DistanceCalculator<ActorWithBody> { t: ActorWithBody, p: PointND ->
@@ -449,7 +490,7 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
val dist2 = (p.getOrd(0) - (t.hitbox.centeredX - world.width * TILE_SIZE)).sqr() + (p.getOrd(1) - t.hitbox.centeredY).sqr()
val dist3 = (p.getOrd(0) - (t.hitbox.centeredX + world.width * TILE_SIZE)).sqr() + (p.getOrd(1) - t.hitbox.centeredY).sqr()
minOf(dist1, minOf(dist2, dist3))
minOf(dist1, dist2, dist3)
}
/**
@@ -467,7 +508,7 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
fun getActorsAt(worldX: Double, worldY: Double): List<ActorWithBody> {
val outList = ArrayList<ActorWithBody>()
try {
actorsRTree.find(worldX, worldY, worldX + 1.0, worldY + 1.0, outList)
actorsRTree.find(worldX, worldY, worldX, worldY, outList)
}
catch (e: NullPointerException) {}
return outList

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.JsonValue
import net.torvald.terrarum.App.*
import net.torvald.terrarum.App.setToGameConfig
import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.WireCodex
import net.torvald.terrarum.gameitems.GameItem

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum
import net.torvald.random.XXHash32
import org.dyn4j.geometry.Vector2
/**
@@ -115,6 +116,10 @@ class Point2i() {
return this
}
override fun hashCode(): Int = XXHash32.hashGeoCoord(x, y)
override fun toString() = "Point2i($x, $y)"
operator fun component1() = x
operator fun component2() = y
}

View File

@@ -0,0 +1,334 @@
package net.torvald.terrarum;
import com.badlogic.gdx.utils.JsonValue;
import net.torvald.terrarum.utils.JsonFetcher;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Bootstrapper that launches the bundled JVM and injects VM configs such as -Xmx
*
* Created by minjaesong on 2023-06-22.
*/
public class Principii {
private static KVHashMap gameConfig = new KVHashMap();
private static String OSName = System.getProperty("os.name");
private static String operationSystem;
/** %appdata%/Terrarum, without trailing slash */
private static String defaultDir;
/** defaultDir + "/config.json" */
private static String configDir;
public static void getDefaultDirRoot() {
String OS = OSName.toUpperCase();
if (OS.contains("WIN")) {
operationSystem = "WINDOWS";
defaultDir = System.getenv("APPDATA") + "/Terrarum";
}
else if (OS.contains("OS X") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond
operationSystem = "OSX";
defaultDir = System.getProperty("user.home") + "/Library/Application Support/Terrarum";
}
else if (OS.contains("NUX") || OS.contains("NIX") || OS.contains("BSD")) {
operationSystem = "LINUX";
defaultDir = System.getProperty("user.home") + "/.Terrarum";
}
else if (OS.contains("SUNOS")) {
operationSystem = "SOLARIS";
defaultDir = System.getProperty("user.home") + "/.Terrarum";
}
else {
operationSystem = "UNKNOWN";
defaultDir = System.getProperty("user.home") + "/.Terrarum";
}
}
public static void main(String[] args) {
boolean devMode = false;
// if -ea flag is set, turn on all the debug prints
try {
assert false;
}
catch (AssertionError e) {
devMode = true;
}
String extracmd = devMode ? " -ea" : "";
String OS = OSName.toUpperCase();
String CPUARCH = System.getProperty("os.arch").toUpperCase();
String runtimeRoot;
String runtimeArch;
if (!CPUARCH.equals("AMD64") && !CPUARCH.equals("X86_64") && !CPUARCH.equals("AARCH64")) { // macOS Rosetta2 reports X86_64
System.err.println("Unsupported CPU architecture: " + CPUARCH);
System.exit(1);
return;
}
else {
runtimeArch = (CPUARCH.equals("AMD64") || CPUARCH.equals("X86_64")) ? "x86" : "arm";
}
if (OS.contains("WIN")) {
runtimeRoot = "runtime-windows-" + runtimeArch;
}
else if (OS.contains("OS X") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond
runtimeRoot = "runtime-osx-" + runtimeArch;
extracmd += " -XstartOnFirstThread";
}
else {
runtimeRoot = "runtime-linux-" + runtimeArch;
extracmd += " -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd";
}
String runtime = new File("out/"+runtimeRoot+"/bin/java").getAbsolutePath();
System.out.println("Runtime path: "+runtime);
getDefaultDirRoot();
configDir = defaultDir + "/config.json";
initialiseConfig();
readConfigJson();
int xmx = getConfigInt("jvm_xmx");
String userDefinedExtraCmd = getConfigString("jvm_extra_cmd").trim();
if (!userDefinedExtraCmd.isEmpty()) userDefinedExtraCmd = " "+userDefinedExtraCmd;
try {
String[] cmd = (runtime+extracmd+userDefinedExtraCmd+" -Xms1G -Xmx"+xmx+"G -cp ./out/TerrarumBuild.jar net.torvald.terrarum.App").split(" ");
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.inheritIO();
System.exit(pb.start().waitFor());
}
catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// CONFIG //
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
private static int getConfigInt(String key) {
Object cfg = getConfigMaster(key);
if (cfg instanceof Integer) return ((int) cfg);
double value = (double) cfg;
if (Math.abs(value % 1.0) < 0.00000001)
return (int) Math.round(value);
return ((int) cfg);
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
private static double getConfigDouble(String key) {
Object cfg = getConfigMaster(key);
return (cfg instanceof Integer) ? (((Integer) cfg) * 1.0) : ((double) (cfg));
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
private static String getConfigString(String key) {
Object cfg = getConfigMaster(key);
return ((String) cfg);
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist. If the default value is undefined, will return false.
*/
private static boolean getConfigBoolean(String key) {
try {
Object cfg = getConfigMaster(key);
return ((boolean) cfg);
}
catch (NullPointerException keyNotFound) {
return false;
}
}
/*private static int[] getConfigIntArray(String key) {
Object cfg = getConfigMaster(key);
if (cfg instanceof JsonArray) {
JsonArray jsonArray = ((JsonArray) cfg).getAsJsonArray();
//return IntArray(jsonArray.size(), { i -> jsonArray[i].asInt })
int[] intArray = new int[jsonArray.size()];
for (int i = 0; i < jsonArray.size(); i++) {
intArray[i] = jsonArray.get(i).getAsInt();
}
return intArray;
}
else
return ((int[]) cfg);
}*/
private static double[] getConfigDoubleArray(String key) {
Object cfg = getConfigMaster(key);
return ((double[]) cfg);
}
private static int[] getConfigIntArray(String key) {
double[] a = getConfigDoubleArray(key);
int[] r = new int[a.length];
for (int i = 0; i < a.length; i++) {
r[i] = ((int) a[i]);
}
return r;
}
/*private static String[] getConfigStringArray(String key) {
Object cfg = getConfigMaster(key);
if (cfg instanceof JsonArray) {
JsonArray jsonArray = ((JsonArray) cfg).getAsJsonArray();
//return IntArray(jsonArray.size(), { i -> jsonArray[i].asInt })
String[] intArray = new String[jsonArray.size()];
for (int i = 0; i < jsonArray.size(); i++) {
intArray[i] = jsonArray.get(i).getAsString();
}
return intArray;
}
else
return ((String[]) cfg);
}*/
/**
* Get config from config file. If the entry does not exist, get from defaults; if the entry is not in the default, NullPointerException will be thrown
*/
private static HashMap<String, Object> getDefaultConfig() {
return DefaultConfig.INSTANCE.getHashMap();
}
private static Object getConfigMaster(String key1) {
String key = key1.toLowerCase();
Object config;
try {
config = gameConfig.get(key);
}
catch (NullPointerException e) {
config = null;
}
Object defaults;
try {
defaults = getDefaultConfig().get(key);
}
catch (NullPointerException e) {
defaults = null;
}
if (config == null) {
if (defaults == null) {
throw new NullPointerException("key not found: '" + key + "'");
}
else {
return defaults;
}
}
else {
return config;
}
}
/**
*
* @return true on successful, false on failure.
*/
private static Boolean readConfigJson() {
System.out.println("Config file: " + configDir);
try {
// read from disk and build config from it
JsonValue map = JsonFetcher.INSTANCE.invoke(configDir);
// make config
for (JsonValue entry = map.child; entry != null; entry = entry.next) {
setToGameConfigForced(entry, null);
}
return true;
}
catch (IOException e) {
// write default config to game dir. Call th.is method again to read config from it.
e.printStackTrace();
return false;
}
}
/**
* Reads DefaultConfig to populate the gameConfig
*/
private static void initialiseConfig() {
for (Map.Entry<String, Object> entry : DefaultConfig.INSTANCE.getHashMap().entrySet()) {
gameConfig.set(entry.getKey(), entry.getValue());
}
}
/**
* Will forcibly overwrite previously loaded config value.
*
* Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName.
*
* @param value JsonValue (the key-value pair)
* @param modName module name, nullable
*/
private static void setToGameConfigForced(JsonValue value, String modName) {
gameConfig.set((modName == null) ? value.name : modName+":"+value.name,
value.isArray() ? value.asDoubleArray() :
value.isDouble() ? value.asDouble() :
value.isBoolean() ? value.asBoolean() :
value.isLong() ? value.asInt() :
value.asString()
);
}
}

View File

@@ -0,0 +1,184 @@
package net.torvald.terrarum
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.savegame.DiskSkimmer
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.io.path.Path
/**
* Created by minjaesong on 2023-06-24.
*/
class SavegameCollection(files0: List<DiskSkimmer>) {
/** Sorted in reverse by the last modified time of the files, index zero being the most recent */
val files = files0.sortedByDescending {
it.getLastModifiedTime().shl(2) or
it.diskFile.extension.matches(Regex("^[abc]${'$'}")).toLong(1) or
it.diskFile.extension.isBlank().toLong(0)
}
/** Sorted in reverse by the last modified time of the files, index zero being the most recent */
val autoSaves = files.filter { it.diskFile.extension.matches(Regex("[a-z]")) }
/** Sorted in reverse by the last modified time of the files, index zero being the most recent */
val manualSaves = files.filter { !it.diskFile.extension.matches(Regex("[a-z]")) }
init {
files.forEach { it.rebuild() }
}
companion object {
fun collectFromBaseFilename(basedir: File, name: String): SavegameCollection {
val files = basedir.listFiles().filter { it.name.startsWith(name) }
.mapNotNull { try { DiskSkimmer(it, true) } catch (e: Throwable) { null } }
return SavegameCollection(files)
}
}
/**
* Returns the most recent not-corrupted file
*/
fun loadable(): DiskSkimmer {
return files.first()
}
fun moveToRecycle(basedir: String) {
files.forEach {
try {
Files.move(it.diskFile.toPath(), Path(basedir, it.diskFile.name), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
}
catch (e: IOException) {
e.printStackTrace()
}
}
}
fun getBaseFile(): DiskSkimmer {
return files.first { it.diskFile.extension.isBlank() }
}
}
class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollection?) {
private var manualPlayer: DiskSkimmer? = null
private var manualWorld: DiskSkimmer? = null
private var autoPlayer: DiskSkimmer? = null
private var autoWorld: DiskSkimmer? = null
var status = 0 // 0: none available, 1: loadable manual save is newer than loadable auto; 2: loadable autosave is newer than loadable manual
private set
var newerSaveIsDamaged = false // only when most recent save is corrupted
private set
init {
printdbg(this, "init ($player, $world)")
if (player != null && world != null) {
printdbg(this, "player files: " + player.files.joinToString { it.diskFile.name })
printdbg(this, "world files:" + world.files.joinToString { it.diskFile.name })
// if a pair of files were saved successfully, they must have identical lastModifiedTime()
var pc = 0; val pt = player.files[0].getLastModifiedTime()
var wc = 0; val wt = world.files[0].getLastModifiedTime()
while (pc < player.files.size && wc < world.files.size) {
val pcf = player.files[pc]
val pcm = pcf.getLastModifiedTime()
val wcf = world.files[wc]
val wcm = wcf.getLastModifiedTime()
printdbg(this, "pc=$pc, wc=$wc, pcm=$pcm, wcm=$wcm")
if (playerDiskNotDamaged(pcf) && worldDiskNotDamaged(wcf)) {
printdbg(this, "pcf.autosaved=${pcf.isAutosaved()}, wcf.autosaved=${wcf.isAutosaved()}")
when (pcf.isAutosaved().toInt(1) or wcf.isAutosaved().toInt()) {
3 -> {
if (autoPlayer == null && autoWorld == null) {
autoPlayer = pcf
autoWorld = wcf
}
pc += 1
wc += 1
}
0 -> {
if (manualPlayer == null && manualWorld == null) {
manualPlayer = pcf
manualWorld = wcf
}
pc += 1
wc += 1
}
else -> {
if (pcm > wcm)
pc += 1
else if (pcm == wcm) {
pc += 1
wc += 1
}
else
wc += 1
}
}
}
if (manualPlayer != null && manualWorld != null && autoPlayer != null && autoWorld != null)
break
}
if (manualPlayer != null && manualWorld != null && autoPlayer != null && autoWorld != null) {
status = if (manualPlayer!!.getLastModifiedTime() > autoPlayer!!.getLastModifiedTime()) 1 else 2
}
else if (manualPlayer != null && manualWorld != null || autoPlayer != null && autoWorld != null) {
status = 1
}
else {
status = 0
}
printdbg(this, "manualPlayer = ${manualPlayer?.diskFile?.path}")
printdbg(this, "manualWorld = ${manualWorld?.diskFile?.path}")
printdbg(this, "autoPlayer = ${autoPlayer?.diskFile?.path}")
printdbg(this, "autoWorld = ${autoWorld?.diskFile?.path}")
printdbg(this, "status = $status")
}
}
private fun DiskSkimmer.isAutosaved() = this.getSaveMode().and(0b0000_0010) != 0
private fun playerDiskNotDamaged(disk: DiskSkimmer): Boolean {
return true
}
private fun worldDiskNotDamaged(disk: DiskSkimmer): Boolean {
return true
}
fun moreRecentAutosaveAvailable() = (status == 2)
fun saveAvaliable() = (status > 0)
fun getManualSave(): DiskPair? {
if (status == 0) return null
return DiskPair(manualPlayer!!, manualWorld!!)
}
fun getAutoSave(): DiskPair? {
if (status != 2) return null
return DiskPair(autoPlayer!!, autoWorld!!)
}
fun getLoadableSave(): DiskPair? {
if (status == 0) return null
return if (manualPlayer != null && manualWorld != null)
DiskPair(manualPlayer!!, manualWorld!!)
else
DiskPair(autoPlayer!!, autoWorld!!)
}
}
data class DiskPair(val player: DiskSkimmer, val world: DiskSkimmer)

View File

@@ -31,6 +31,8 @@ import net.torvald.terrarum.savegame.DiskSkimmer
import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.utils.JsonFetcher
import net.torvald.terrarum.utils.forEachSiblings
import net.torvald.terrarum.worlddrawer.WorldCamera
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import net.torvald.unsafe.UnsafeHelper
@@ -62,11 +64,11 @@ object Terrarum : Disposable {
*/
const val PLAYER_REF_ID: Int = 0x91A7E2
inline fun inShapeRenderer(shapeRendererType: ShapeRenderer.ShapeType = ShapeRenderer.ShapeType.Filled, action: (ShapeRenderer) -> Unit) {
/*inline fun inShapeRenderer(shapeRendererType: ShapeRenderer.ShapeType = ShapeRenderer.ShapeType.Filled, action: (ShapeRenderer) -> Unit) {
shapeRender.begin(shapeRendererType)
action(shapeRender)
shapeRender.end()
}
}*/
var blockCodex = BlockCodex(); internal set
@@ -628,6 +630,7 @@ fun Float.sqrt() = FastMath.sqrt(this)
fun Int.abs() = this.absoluteValue
fun Double.bipolarClamp(limit: Double) = this.coerceIn(-limit, limit)
fun Boolean.toInt(shift: Int = 0) = if (this) 1.shl(shift) else 0
fun Boolean.toLong(shift: Int = 0) = if (this) 1L.shl(shift) else 0L
fun Int.bitCount() = java.lang.Integer.bitCount(this)
fun Long.bitCount() = java.lang.Long.bitCount(this)
@@ -797,15 +800,17 @@ fun AppUpdateListOfSavegames() {
}
}.sortedByDescending { it.getLastModifiedTime() }.forEachIndexed { index, it ->
println("${index+1}.\t${it.diskFile.absolutePath}")
it.rebuild() // disk skimmer was created without initialisation, so do it now
it.rebuild()
val jsonFile = it.getFile(SAVEGAMEINFO)!!
val json = JsonReader().parse(ByteArray64Reader(jsonFile.bytes, Common.CHARSET).readText())
val worldUUID = UUID.fromString(json.getString("worldIndex"))
var worldUUID: UUID? = null
JsonFetcher.readFromJsonString(ByteArray64Reader(jsonFile.bytes, Common.CHARSET)).forEachSiblings { name, value ->
if (name == "worldIndex") worldUUID = UUID.fromString(value.asString())
}
// if multiple valid savegames with same UUID exist, only the most recent one is retained
if (!App.savegameWorlds.contains(worldUUID)) {
App.savegameWorlds[worldUUID] = it
App.savegameWorlds[worldUUID] = SavegameCollection.collectFromBaseFilename(File(worldsDir), it.diskFile.name)
App.savegameWorldsName[worldUUID] = it.getDiskName(Common.CHARSET)
App.sortedSavegameWorlds.add(worldUUID)
}
@@ -827,15 +832,17 @@ fun AppUpdateListOfSavegames() {
}
}.sortedByDescending { it.getLastModifiedTime() }.forEachIndexed { index, it ->
println("${index+1}.\t${it.diskFile.absolutePath}")
it.rebuild() // disk skimmer was created without initialisation, so do it now
it.rebuild()
val jsonFile = it.getFile(SAVEGAMEINFO)!!
val json = JsonReader().parse(ByteArray64Reader(jsonFile.bytes, Common.CHARSET).readText())
val playerUUID = UUID.fromString(json.getString("uuid"))
var playerUUID: UUID? = null
JsonFetcher.readFromJsonString(ByteArray64Reader(jsonFile.bytes, Common.CHARSET)).forEachSiblings { name, value ->
if (name == "uuid") playerUUID = UUID.fromString(value.asString())
}
// if multiple valid savegames with same UUID exist, only the most recent one is retained
if (!App.savegamePlayers.contains(playerUUID)) {
App.savegamePlayers[playerUUID] = it
App.savegamePlayers[playerUUID] = SavegameCollection.collectFromBaseFilename(File(playersDir), it.diskFile.name)
App.savegamePlayersName[playerUUID] = it.getDiskName(Common.CHARSET)
App.sortedPlayers.add(playerUUID)
}

View File

@@ -31,7 +31,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
const val DEFAULT_LOADORDER_FILE = """# Load Order
# Modules are loaded from top to bottom.
# You can disable basegame, but we don't recommend.
# Name of the module corresponds with the name of the directory the module is stored in,
# typically under:
# 1. assets/mods of the installation path (the modules comes with the release of the game)
# 2. %APPDATA%/Modules (the modules installed by the user)
# where %APPDATA% is:
# Windows -- C:\Users\<username>\AppData\Roaming\Terrarum
# macOS -- /Users/<username>/Library/Application Support/Terrarum
# Linux -- /home/<username>/.Terrarum
# Please refrain from removing 'basegame' on the load order -- it may render the game unpalyable.
basegame
"""

View File

@@ -33,14 +33,16 @@ class TerrarumScreenSize(scrw: Int = defaultW, scrh: Int = defaultH) {
val tvSafeActionWidth: Int; get() = Math.round(width * TV_SAFE_ACTION)
val tvSafeActionHeight: Int; get() = Math.round(height * TV_SAFE_ACTION)
/** Apparent window size. `roundToEven(width * magn)` */
var windowW: Int = 0; private set
/** Apparent window size. `roundToEven(height * magn)` */
var windowH: Int = 0; private set
init {
setDimension(maxOf(minimumW, scrw), maxOf(minimumH, scrh), App.getConfigDouble("screenmagnifying").toFloat(), maxOf(minimumW, scrw), maxOf(minimumH, scrh))
setDimension(maxOf(minimumW, scrw), maxOf(minimumH, scrh), App.getConfigDouble("screenmagnifying").toFloat())
}
fun setDimension(scrw: Int, scrh: Int, magn: Float, ww: Int, wh: Int) {
fun setDimension(scrw: Int, scrh: Int, magn: Float,) {
width = scrw and 0x7FFFFFFE
height = scrh and 0x7FFFFFFE
wf = scrw.toFloat()
@@ -54,11 +56,11 @@ class TerrarumScreenSize(scrw: Int = defaultW, scrh: Int = defaultH) {
this.magn = magn
windowW = ww
windowH = wh
windowW = (scrw * magn).ceilInt() and 0x7FFFFFFE
windowH = (scrh * magn).ceilInt() and 0x7FFFFFFE
printdbg(this, "Window dim: $ww x $wh, called by:")
printdbg(this, "Window dim: $windowW x $windowH, called by:")
printStackTrace(this)
}

View File

@@ -151,4 +151,8 @@ class WireCodex {
printdbg(this, "Setting prop ${prop.id} ->>\t${prop.nameKey}")
}
fun getAllWiresThatAccepts(accept: String): List<Pair<ItemID, WireProp>> {
return wireProps.filter { it.value.accepts == accept }.toList()
}
}

View File

@@ -16,7 +16,7 @@ object ScreencapNogui: ConsoleCommand {
PixmapIO2.writeTGA(Gdx.files.absolute(App.defaultDir + "/Exports/${args[1]}.tga"), p, true)
p.dispose()
}
IngameRenderer.screencapRequested = true
IngameRenderer.requestScreencap()
Echo("FBO exported to$ccG Exports/${args[1]}.tga")
}
else {

View File

@@ -13,10 +13,20 @@ import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.floor
/**
* Used as construction markers and fixture ghost images
* Used as construction markers and fixture ghost images.
*
* `isVisible` behaves differently by `markerMode`.
* - FIXTURE_GHOST: `isVisible` toggles if the ghost is being updated. FALSE - will not be updated and also not visible
* - BLOCK_MARKER: `isVisible` controls the visibility. FALSE - invisible, TRUE - always visible
*
* MarkerMode must be set manually after calling `setGhost` -- the `unsetGhost` will not reset the field.
*/
class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = PhysProperties.MOBILE_OBJECT), NoSerialise {
enum class MarkerMode {
FIXTURE_GHOST, BLOCK_MARKER
}
private val defaultSize = 16.0
override var referenceID: ActorID = 2147483647 // custom refID
@@ -29,7 +39,7 @@ class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = Phy
get() = CommonResourcePool.getAsTextureRegionPack("blockmarkings_common")
private var ghost: SpriteAnimation? = null
private var hasGhost = false
var markerMode: MarkerMode = MarkerMode.FIXTURE_GHOST
var ghostColour = Color.WHITE
@@ -38,9 +48,10 @@ class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = Phy
renderOrder = Actor.RenderOrder.OVERLAY // for some reason the constructor didn't work
}
override fun drawBody(batch: SpriteBatch) {
if (isVisible) {
if (hasGhost) {
if (markerMode == MarkerMode.FIXTURE_GHOST) {
if (INGAME.actorNowPlaying != null) {
mouseInInteractableRange(INGAME.actorNowPlaying!!) {
batch.shader = App.shaderGhastlyWhite
@@ -59,7 +70,7 @@ class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = Phy
}
}
}
else {
else if (markerMode == MarkerMode.BLOCK_MARKER) {
batch.shader = null
batch.color = markerColour
batch.draw(blockMarkings.get(markerShape, 0), hitbox.startX.toFloat(), hitbox.startY.toFloat())
@@ -91,13 +102,12 @@ class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = Phy
fun setGhost(actor: ActorWithBody) {
ghost = actor.sprite
hasGhost = true
markerMode = MarkerMode.FIXTURE_GHOST
hitbox.setDimension(actor.baseHitboxW.toDouble(), actor.baseHitboxH.toDouble())
}
fun unsetGhost() {
ghost = null
hasGhost = false
setGhostColourNone()
hitbox.setDimension(TILE_SIZED, TILE_SIZED)
}

View File

@@ -39,10 +39,12 @@ class SimpleGameWorld : GameWorld() {
override lateinit var layerTerrain: BlockLayer
}
open class GameWorld() : Disposable {
open class GameWorld(
val worldIndex: UUID // should not be immutable as JSON loader will want to overwrite it
) : Disposable {
constructor() : this(UUID.randomUUID())
// var worldName: String = "New World"
var worldIndex: UUID = UUID.randomUUID() // should not be immutable as JSON loader will want to overwrite it
var worldCreator: UUID = UUID(0L,0L) // TODO record a value to this
var width: Int = 999; private set
var height: Int = 999; private set
@@ -387,14 +389,14 @@ open class GameWorld() : Disposable {
return wiringGraph[blockAddr]?.get(itemID)?.emt
}
fun getWireRecvStateOf(x: Int, y: Int, itemID: ItemID): ArrayList<WireRecvState>? {
fun getWireReceptionStateOf(x: Int, y: Int, itemID: ItemID): ArrayList<WireReceptionState>? {
val (x, y) = coerceXY(x, y)
val blockAddr = LandUtil.getBlockAddr(this, x, y)
return getWireRecvStateUnsafe(blockAddr, itemID)
return getWireReceptionStateUnsafe(blockAddr, itemID)
}
fun getWireRecvStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): ArrayList<WireRecvState>? {
return wiringGraph[blockAddr]?.get(itemID)?.rcv
fun getWireReceptionStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): ArrayList<WireReceptionState>? {
return wiringGraph[blockAddr]?.get(itemID)?.rcp
}
fun setWireGraphOf(x: Int, y: Int, itemID: ItemID, cnx: Int) {
@@ -427,7 +429,7 @@ open class GameWorld() : Disposable {
wiringGraph[blockAddr]!![itemID]!!.emt.set(vector)
}
fun addWireRecvStateOf(x: Int, y: Int, itemID: ItemID, state: WireRecvState) {
fun addWireRecvStateOf(x: Int, y: Int, itemID: ItemID, state: WireReceptionState) {
val (x, y) = coerceXY(x, y)
val blockAddr = LandUtil.getBlockAddr(this, x, y)
return addWireRecvStateOfUnsafe(blockAddr, itemID, state)
@@ -439,13 +441,13 @@ open class GameWorld() : Disposable {
return clearAllWireRecvStateUnsafe(blockAddr)
}
fun addWireRecvStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, state: WireRecvState) {
fun addWireRecvStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, state: WireReceptionState) {
if (wiringGraph[blockAddr] == null)
wiringGraph[blockAddr] = WiringGraphMap()
if (wiringGraph[blockAddr]!![itemID] == null)
wiringGraph[blockAddr]!![itemID] = WiringSimCell(0)
wiringGraph[blockAddr]!![itemID]!!.rcv.add(state)
wiringGraph[blockAddr]!![itemID]!!.rcp.add(state)
}
fun getAllWiringGraph(x: Int, y: Int): HashMap<ItemID, WiringSimCell>? {
@@ -460,7 +462,7 @@ open class GameWorld() : Disposable {
fun clearAllWireRecvStateUnsafe(blockAddr: BlockAddress) {
wiringGraph[blockAddr]?.forEach {
it.value.rcv.clear()
it.value.rcp.clear()
}
}
@@ -652,7 +654,7 @@ open class GameWorld() : Disposable {
val ws: SortedArrayList<ItemID> = SortedArrayList<ItemID>() // what could possibly go wrong bloating up the RAM footprint when it's practically infinite these days?
)
data class WireRecvState(
data class WireReceptionState(
var dist: Int = -1, // how many tiles it took to traverse
var src: Point2i = Point2i(0,0) // xy position
// to get the state, use the src to get the state of the source emitter directly, then use dist to apply attenuation
@@ -664,7 +666,7 @@ open class GameWorld() : Disposable {
data class WiringSimCell(
var cnx: Int = 0, // connections. [1, 2, 4, 8] = [RIGHT, DOWN, LEFT, UP]
val emt: Vector2 = Vector2(0.0, 0.0), // i'm emitting this much power
val rcv: ArrayList<WireRecvState> = ArrayList() // how far away are the power sources
val rcp: ArrayList<WireReceptionState> = ArrayList() // how far away are the power sources
)
fun getTemperature(worldTileX: Int, worldTileY: Int): Float? {

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.gameworld
import com.badlogic.gdx.utils.Queue
import net.torvald.terrarum.*
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameactors.ActorWithBody
@@ -468,14 +469,15 @@ object WorldSimulator {
/**
* @return List of FixtureBases, safe to cast into Electric
*/
private fun wiresimGetSourceBlocks(): List<FixtureBase> =
INGAME.actorContainerActive.filterIsInstance<FixtureBase>().filter {
it is Electric && it.inUpdateRange(world) && it.wireEmitterTypes.isNotEmpty()
private fun wiresimGetSourceBlocks(): List<Electric> =
INGAME.actorContainerActive.filterIsInstance<Electric>().filter {
it.inUpdateRange(world) && it.wireEmitterTypes.isNotEmpty()
}
private val wireSimMarked = HashSet<Long>()
private val wireSimPoints = Queue<WireGraphCursor>()
private val oldTraversedNodes = ArrayList<WireGraphCursor>()
private val fixtureCache = HashMap<Point2i, Pair<Electric, WireEmissionType>>() // also instance of Electric
private fun simulateWires(delta: Float) {
// unset old wires before we begin
@@ -484,14 +486,15 @@ object WorldSimulator {
}
oldTraversedNodes.clear()
fixtureCache.clear()
wiresimGetSourceBlocks().let { sources ->
// signal-emitting fixtures must set emitState of its own tiles via update()
sources.forEach {
(it as Electric).wireEmitterTypes.forEach { wireType, bbi ->
it.wireEmitterTypes.forEach { bbi, wireType ->
val startingPoint = it.worldBlockPos!! + it.blockBoxIndexToPoint2i(bbi)
val signal = (it as Electric).wireEmission[bbi] ?: Vector2(0.0, 0.0)
val signal = it.wireEmission[bbi] ?: Vector2(0.0, 0.0)
world.getAllWiringGraph(startingPoint.x, startingPoint.y)?.keys?.filter { WireCodex[it].accepts == wireType }?.forEach { wire ->
val simStartingPoint = WireGraphCursor(startingPoint, wire)
@@ -506,6 +509,8 @@ object WorldSimulator {
private fun traverseWireGraph(world: GameWorld, wire: ItemID, startingPoint: WireGraphCursor, signal: Vector2) {
val emissionType = WireCodex[wire].accepts
fun getAdjacent(cnx: Int, point: WireGraphCursor): List<WireGraphCursor> {
val r = ArrayList<WireGraphCursor>()
for (dir in intArrayOf(RIGHT, DOWN, LEFT, UP)) {
@@ -540,6 +545,28 @@ object WorldSimulator {
enq(x)
}
}
// do something with the power receiver
val tilePoint = Point2i(point.x, point.y)
var fixture = fixtureCache[tilePoint]
var tileOffsetFromFixture: Point2i? = null
if (fixture == null) {
INGAME.getActorsAt(point.x * TILE_SIZED, point.y * TILE_SIZED).filterIsInstance<Electric>().firstOrNull().let { found ->
if (found != null) {
// get offset from the fixture's origin
tileOffsetFromFixture = found.intTilewiseHitbox.let { Point2i(it.startX.toInt(), it.startY.toInt()) } - tilePoint
// println("$tilePoint; ${found.javaClass.canonicalName}, $tileOffsetFromFixture, ${found.getWireSinkAt(tileOffsetFromFixture!!)}")
if (found.getWireSinkAt(tileOffsetFromFixture!!) == emissionType) {
fixtureCache[tilePoint] = found to emissionType
fixture = found to emissionType
}
}
}
}
fixture?.first?.updateOnWireGraphTraversal(tileOffsetFromFixture!!.x, tileOffsetFromFixture!!.y, fixture!!.second)
}
}
}

View File

@@ -11,7 +11,7 @@ import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.App.measureDebugTime
import net.torvald.terrarum.App.*
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.gameactors.ActorWithBody
@@ -21,6 +21,7 @@ import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.weather.WeatherMixer.render
import net.torvald.terrarum.worlddrawer.BlocksDrawer
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.worlddrawer.LightmapRenderer
@@ -204,7 +205,7 @@ object IngameRenderer : Disposable {
actorsRenderOverlay: List<ActorWithBody>,
particlesContainer : CircularArray<ParticleBase>,
player: ActorWithBody? = null,
uiContainer: UIContainer? = null
uiContainer: UIContainer? = null,
) {
renderingActorsCount = (actorsRenderBehind.size) +
(actorsRenderMiddle.size) +
@@ -365,13 +366,19 @@ object IngameRenderer : Disposable {
///////////////////////////////////////////////////////////////////////
if (screencapRequested) {
screencapRequested = false
printdbg(this, "Screencap was requested, processing...")
var hasError = false
try {
screencapExportCallback(fboMixedOut)
}
catch (e: Throwable) {
printdbgerr(this, "An error occured while taking screencap:")
e.printStackTrace()
hasError = true
}
printdbg(this, "Screencap ${if (hasError) "failed" else "successful"}")
screencapBusy = false
screencapRequested = false
}
///////////////////////////////////////////////////////////////////////
@@ -416,11 +423,18 @@ object IngameRenderer : Disposable {
* This "screencap" will capture the game WITHOUT gui and postprocessors!
* To capture the entire game, use [App.requestScreenshot]
*/
@Volatile internal var screencapRequested = false
@Volatile internal var fboRGBexportedLatch = false
@Volatile private var screencapRequested = false
@Volatile internal var screencapBusy = false; private set
@Volatile internal var screencapExportCallback: (FrameBuffer) -> Unit = {}
@Volatile internal lateinit var fboRGBexport: Pixmap
fun requestScreencap() {
screencapRequested = true
screencapBusy = true
printdbg(this, "requestScreencap called from:")
printStackTrace(this)
}
private fun drawToRGB(
actorsRenderBehind: List<ActorWithBody>?,
actorsRenderMiddle: List<ActorWithBody>?,

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.*
import net.torvald.terrarum.App.*
@@ -26,6 +27,7 @@ import net.torvald.terrarum.gameitems.mouseInInteractableRange
import net.torvald.terrarum.gameparticles.ParticleBase
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldSimulator
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.*
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
import net.torvald.terrarum.modulebasegame.gameitems.PickaxeCore
@@ -34,6 +36,7 @@ import net.torvald.terrarum.modulebasegame.serialise.LoadSavegame
import net.torvald.terrarum.modulebasegame.serialise.ReadActor
import net.torvald.terrarum.modulebasegame.serialise.WriteSavegame
import net.torvald.terrarum.modulebasegame.ui.*
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.gradEndCol
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen
import net.torvald.terrarum.modulebasegame.worldgenerator.WorldgenParams
@@ -42,6 +45,7 @@ import net.torvald.terrarum.savegame.VDUtil
import net.torvald.terrarum.savegame.VirtualDisk
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.Toolkit.hdrawWidth
import net.torvald.terrarum.ui.UIAutosaveNotifier
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.weather.WeatherMixer
@@ -53,6 +57,7 @@ import net.torvald.unicode.EMDASH
import net.torvald.util.CircularArray
import org.khelekore.prtree.PRTree
import java.util.*
import kotlin.math.roundToInt
/**
@@ -260,7 +265,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
}
IngameRenderer.setRenderedWorld(world)
blockMarkingActor.isVisible = true
super.show() // this function sets gameInitialised = true
}
@@ -418,10 +423,15 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// 2. cannot sync up the "counter" to determine whether both are finished
uiAutosaveNotifier.setAsOpen()
val saveTime_t = App.getTIME_T()
printdbg(this, "Immediate Save")
WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, true, autosaveOnErrorAction) {
printdbg(this, "immediate save callback from PLAYER")
makeSavegameBackupCopy(getPlayerSaveFiledesc(playerSavefileName))
WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.WORLD, worldDisk, getWorldSaveFiledesc(worldSavefileName), this, true, autosaveOnErrorAction) {
printdbg(this, "immediate save callback from WORLD")
makeSavegameBackupCopy(getWorldSaveFiledesc(worldSavefileName)) // don't put it on the postInit() or render(); must be called using callback
uiAutosaveNotifier.setAsClose()
}
@@ -470,13 +480,14 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
world.worldCreator = UUID.fromString(player.uuid.toString())
printdbg(this, "new woridIndex: ${world.worldIndex}")
printdbg(this, "new worldIndex: ${world.worldIndex}")
printdbg(this, "worldCurrentlyPlaying: ${player.worldCurrentlyPlaying}")
actorNowPlaying = player
actorGamer = player
forceAddActor(player)
WeatherMixer.internalReset()
}
KeyToggler.forceSet(Input.Keys.Q, false)
@@ -521,7 +532,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// pie menu
uiPieMenu = UIQuickslotPie()
uiPieMenu.setPosition(drawWidth / 2, App.scr.halfh)
uiPieMenu.setPosition(hdrawWidth, App.scr.halfh)
// vital metre
// fill in getter functions by
@@ -715,6 +726,10 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
gameUpdateGovernor.reset()
if (UILoadGovernor.previousSaveWasLoaded) {
sendNotification(listOf(Lang["GAME_PREV_SAVE_WAS_LOADED1"], Lang["GAME_PREV_SAVE_WAS_LOADED2"]))
}
gameFullyLoaded = true
}
@@ -851,7 +866,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
//notifier.update(delta)
// open/close fake blur UI according to what's opened
if (uiInventoryPlayer.isVisible ||
getUIFixture.get()?.isVisible == true) {
getUIFixture.get()?.isVisible == true || worldTransitionOngoing) {
uiBlur.setAsOpen()
}
else {
@@ -867,6 +882,26 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
//println("paused = $paused")
if ((!paused && !App.isScreenshotRequested()) && newWorldLoadedLatch) newWorldLoadedLatch = false
if (doThingsAfterSave) {
saveRequested2 = false
doThingsAfterSave = false
saveCallback!!()
}
if (saveRequested2) {
saveRequested2 = false
doForceSave()
}
if (worldTransitionPauseRequested > 0) { // let a frame to update before locking (=pausing) entirely
worldTransitionPauseRequested -= 1
}
else if (worldTransitionPauseRequested == 0) {
paused = true
}
}
@@ -901,6 +936,93 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
actorNowPlaying,
uiContainer// + uiFixture
)
// quick and dirty way to show
if (worldTransitionOngoing) {
batch.inUse {
batch.color = gradEndCol
Toolkit.fillArea(batch, 0, 0, App.scr.width, App.scr.height)
batch.color = Color.WHITE
val t = Lang["MENU_IO_SAVING"]
val circleSheet = CommonResourcePool.getAsTextureRegionPack("loading_circle_64")
Toolkit.drawTextCentered(batch, App.fontGame, t, Toolkit.drawWidth, 0, ((App.scr.height - circleSheet.tileH) / 2) - 40)
// -1..63
val index =
((WriteSavegame.saveProgress / WriteSavegame.saveProgressMax) * circleSheet.horizontalCount * circleSheet.verticalCount).roundToInt() - 1
if (index >= 0) {
val sx = index % circleSheet.horizontalCount
val sy = index / circleSheet.horizontalCount
// q&d fix for ArrayIndexOutOfBoundsException caused when saving huge world... wut?
if (sx in 0 until circleSheet.horizontalCount && sy in 0 until circleSheet.horizontalCount) {
batch.draw(
circleSheet.get(sx, sy),
((Toolkit.drawWidth - circleSheet.tileW) / 2).toFloat(),
((App.scr.height - circleSheet.tileH) / 2).toFloat()
)
}
}
}
}
}
private var worldTransitionOngoing = false
private var worldTransitionPauseRequested = -1
private var saveRequested2 = false
private var saveCallback: (() -> Unit)? = null
private var doThingsAfterSave = false
override fun requestForceSave(callback: () -> Unit) {
saveCallback = callback
worldTransitionOngoing = true
saveRequested2 = true
worldTransitionPauseRequested = 1
blockMarkingActor.isVisible = false
}
internal fun doForceSave() {
// TODO show appropriate UI
// uiBlur.setAsOpen()
saveTheGame({ // onSuccessful
System.gc()
autosaveTimer = 0f
// TODO hide appropriate UI
uiBlur.setAsClose()
doThingsAfterSave = true
}, { // onError
// TODO show failure message
// TODO hide appropriate UI
uiBlur.setAsClose()
})
}
override fun saveTheGame(onSuccessful: () -> Unit, onError: (Throwable) -> Unit) {
val saveTime_t = App.getTIME_T()
val playerSavefile = getPlayerSaveFiledesc(INGAME.playerSavefileName)
val worldSavefile = getWorldSaveFiledesc(INGAME.worldSavefileName)
INGAME.makeSavegameBackupCopy(playerSavefile)
WriteSavegame(saveTime_t, WriteSavegame.SaveMode.PLAYER, INGAME.playerDisk, playerSavefile, INGAME as TerrarumIngame, false, onError) {
INGAME.makeSavegameBackupCopy(worldSavefile)
WriteSavegame(saveTime_t, WriteSavegame.SaveMode.WORLD, INGAME.worldDisk, worldSavefile, INGAME as TerrarumIngame, false, onError) {
// callback:
// rebuild the disk skimmers
INGAME.actorContainerActive.filterIsInstance<IngamePlayer>().forEach {
printdbg(this, "Game Save callback -- rebuilding the disk skimmer for IngamePlayer ${it.actorValue.getAsString(AVKey.NAME)}")
// it.rebuildingDiskSkimmer?.rebuild()
}
// return to normal state
onSuccessful()
}
}
}
private val maxRenderableWires = ReferencingRanges.ACTORS_WIRES.last - ReferencingRanges.ACTORS_WIRES.first + 1
@@ -1107,13 +1229,13 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
uiAutosaveNotifier.setAsOpen()
val saveTime_t = App.getTIME_T()
val playerSavefile = getPlayerSaveFiledesc(INGAME.playerSavefileName)
val worldSavefile = getWorldSaveFiledesc(INGAME.worldSavefileName)
val playerSavefile0 = getPlayerSaveFiledesc(INGAME.playerSavefileName)
val worldSavefile0 = getWorldSaveFiledesc(INGAME.worldSavefileName)
INGAME.makeSavegameBackupCopy(playerSavefile)
val playerSavefile = INGAME.makeSavegameBackupCopyAuto(playerSavefile0)
WriteSavegame(saveTime_t, WriteSavegame.SaveMode.PLAYER, INGAME.playerDisk, playerSavefile, INGAME as TerrarumIngame, true, autosaveOnErrorAction) {
INGAME.makeSavegameBackupCopy(worldSavefile)
val worldSavefile = INGAME.makeSavegameBackupCopyAuto(worldSavefile0)
WriteSavegame(saveTime_t, WriteSavegame.SaveMode.QUICK_WORLD, INGAME.worldDisk, worldSavefile, INGAME as TerrarumIngame, true, autosaveOnErrorAction) {
// callback:
// rebuild the disk skimmers

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.InputAdapter
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera
@@ -13,17 +14,20 @@ import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.App.printdbgerr
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.console.CommandDict
import net.torvald.terrarum.gameactors.*
import net.torvald.terrarum.gameactors.ai.ActorAI
import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.gameparticles.ParticleBase
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.ui.UILoadGovernor
import net.torvald.terrarum.modulebasegame.ui.UIRemoCon
import net.torvald.terrarum.modulebasegame.ui.UITitleRemoConYaml
import net.torvald.terrarum.realestate.LandUtil
@@ -64,49 +68,72 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
private lateinit var demoWorld: GameWorld
private lateinit var cameraNodes: FloatArray // camera Y-pos
private val cameraNodeWidth = 15
private val lookaheadDist = cameraNodeWidth * TILE_SIZED
private fun getPointAt(px: Double): Double {
val ww = TILE_SIZEF * demoWorld.width
val x = px % ww
val indexThis = ((x / ww * cameraNodes.size).floorInt())
val xwstart: Double = indexThis.toDouble() / cameraNodes.size * ww
val xwend: Double = ((indexThis + 1).toDouble() / cameraNodes.size) * ww
val xw: Double = xwend - xwstart
val xperc: Double = (x - xwstart) / xw
// return FastMath.interpolateLinear(xperc.toFloat(), cameraNodes[indexThis fmod cameraNodes.size], cameraNodes[(indexThis + 1) fmod cameraNodes.size]).toDouble()
return FastMath.interpolateCatmullRom(xperc.toFloat(),
cameraNodes[(indexThis - 1) fmod cameraNodes.size],
cameraNodes[(indexThis - 0) fmod cameraNodes.size],
cameraNodes[(indexThis + 1) fmod cameraNodes.size],
cameraNodes[(indexThis + 2) fmod cameraNodes.size]
).toDouble()
}
private val cameraAI = object : ActorAI {
private var firstTime = true
private val lookaheadDist = 100.0
private fun getPointAt(px: Double): Float {
val ww = TILE_SIZEF * demoWorld.width
val x = px % ww
val indexThis = ((x / ww * cameraNodes.size).floorInt()) fmod cameraNodes.size
val xwstart: Float = indexThis.toFloat() / cameraNodes.size * ww
val xwend: Float = ((indexThis + 1).toFloat() / cameraNodes.size) * ww
val xw: Float = xwend - xwstart
val xperc: Double = (x - xwstart) / xw
return FastMath.interpolateLinear(xperc.toFloat(), cameraNodes[indexThis], cameraNodes[(indexThis + 1) % cameraNodes.size])
}
override fun update(actor: Actor, delta: Float) {
val ww = TILE_SIZEF * demoWorld.width
val actor = actor as CameraPlayer
val px: Double = actor.hitbox.canonicalX + actor.actorValue.getAsDouble(AVKey.SPEED)!!
val pxP = px - lookaheadDist * cos(actor.targetBearing)
val pxN = px + lookaheadDist * cos(actor.targetBearing)
val stride = cos(actor.bearing1A) * actor.actorValue.getAsDouble(AVKey.SPEED)!!
val yP = getPointAt(pxP)
val yN = getPointAt(pxN)
val x1 = actor.hitbox.startX// + stride
val y1 = actor.hitbox.startY
val y = (yP + yN) / 2f
val px1L = x1 - lookaheadDist
val px1C = x1
val px1R = x1 + lookaheadDist
val py1L = getPointAt(px1L)
val py1C = getPointAt(px1C)
val py1R = getPointAt(px1R)
val px2L = (px1L + px1C) / 2.0
val px2R = (px1C + px1R) / 2.0
val py2L = (py1L + py1C) / 2.0
val py2R = (py1C + py1R) / 2.0
val x2 = (px2L + px2R) / 2
val y2 = (py2L + py2R) / 2
val theta = atan2(py2R - py2L, px2R - px2L)
if (firstTime) {
firstTime = false
actor.hitbox.setPositionY(y - 8.0)
actor.hitbox.setPosition(x1, getPointAt(x1))
}
else {
//actor.moveTo(px, y - 8.0)
//actor.hitbox.setPosition(px, y - 8.0)
actor.moveTo(atan2((yN - yP).toDouble(), pxN - pxP))
actor.bearing1A = atan2(py1C - py1L, px1C - px1L)
actor.bearing1B = atan2(py1R - py1C, px1R - px1C)
actor.bearing2A = atan2(py2R - py2L, px2R - px2L)
actor.moveTo(theta)
// actor.hitbox.setPosition(x2, y2) // there is no reason it would work -- speed is wildly inconsistent as the angle reaches 90deg
}
if (actor.hitbox.canonicalX > ww) {
if (actor.hitbox.startX > ww) {
actor.hitbox.translatePosX(-ww.toDouble())
}
}
}
private lateinit var cameraPlayer: ActorWithBody
@@ -122,9 +149,9 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
private lateinit var worldFBO: FloatFrameBuffer
private val warning32bitJavaIcon = TextureRegion(Texture(Gdx.files.internal("assets/graphics/gui/32_bit_warning.tga")))
private val warningAppleRosettaIcon = TextureRegion(Texture(Gdx.files.internal("assets/graphics/gui/apple_rosetta_warning.tga")))
init {
warning32bitJavaIcon.flip(false, false)
gameUpdateGovernor = ConsistentUpdateRate.also { it.reset() }
}
@@ -138,7 +165,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
//ReadWorld.readWorldAndSetNewWorld(Terrarum.ingame!! as TerrarumIngame, reader)
val world = ReadSimpleWorld(reader, file)
demoWorld = world
demoWorld.worldTime.timeDelta = 0//60
demoWorld.worldTime.timeDelta = 330 // a year = 8 minutes
printdbg(this, "Demo world loaded")
}
catch (e: IOException) {
@@ -152,7 +179,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32)
// construct camera nodes
val nodeCount = demoWorld.width / 10
val nodeCount = demoWorld.width / cameraNodeWidth
cameraNodes = kotlin.FloatArray(nodeCount) {
val tileXPos = (demoWorld.width.toFloat() * it / nodeCount).floorInt()
var travelDownCounter = 0
@@ -237,6 +264,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
printdbg(this, "update list of savegames")
// to show "Continue" and "Load" on the titlescreen, uncomment this line
App.updateListOfSavegames()
UILoadGovernor.reset()
loadThingsWhileIntroIsVisible()
@@ -253,9 +281,14 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
}
private val updateScreen = { delta: Float ->
demoWorld.globalLight = WeatherMixer.globalLightNow
// TODO: desynched weather and time-of-day change
val forcedTime = 39693
// demoWorld.globalLight = WeatherMixer.globalLightNow
demoWorld.globalLight = WeatherMixer.getGlobalLightOfTime(demoWorld, forcedTime)
demoWorld.updateWorldTime(delta)
WeatherMixer.update(delta, cameraPlayer, demoWorld)
// WeatherMixer.update(delta, cameraPlayer, demoWorld)
WeatherMixer.forceTimeAt = forcedTime
cameraPlayer.update(delta)
// worldcamera update AFTER cameraplayer in this case; the other way is just an exception for actual ingame SFX
@@ -268,6 +301,24 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
private val particles = CircularArray<ParticleBase>(16, true)
private fun drawLineOnWorld(x1: Float, y1: Float, x2: Float, y2: Float) {
val w = 2.0f
App.shapeRender.rectLine(
x1 - WorldCamera.x, App.scr.height - (y1 - WorldCamera.y),
x2 - WorldCamera.x, App.scr.height - (y2 - WorldCamera.y),
w
)
}
private fun drawLineOnWorld(x1: Double, y1: Double, x2: Double, y2: Double) {
val ww = demoWorld.width * TILE_SIZE
drawLineOnWorld(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat())
drawLineOnWorld(x1.toFloat() + ww, y1.toFloat(), x2.toFloat() + ww, y2.toFloat())
}
private val baseSlopeCol = Color(0f, 0.9f, 0.85f, 1f)
private val firstOrderSlopeCol = Color(0f, 0.4f, 0f, 1f)
private val secondOrderSlopeCol = Color(1f, 0.3f, 0.6f, 1f)
private val renderScreen = { delta: Float ->
Gdx.graphics.setTitle(TerrarumIngame.getCanonicalTitle())
@@ -290,6 +341,52 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
particles,
uiContainer = uiContainer
)
if (KeyToggler.isOn(Input.Keys.F10)) {
App.shapeRender.inUse {
val actor = cameraPlayer as CameraPlayer
val x1 = actor.hitbox.startX
val y1 = actor.hitbox.startY
val px1L = x1 - lookaheadDist// * cos(actor.bearing1A)
val px1C = x1
val px1R = x1 + lookaheadDist// * cos(actor.bearing1B)
val py1L = getPointAt(px1L)
val py1C = getPointAt(px1C)
val py1R = getPointAt(px1R)
val px2L = (px1L + px1C) / 2.0
val px2R = (px1C + px1R) / 2.0
val py2L = (py1L + py1C) / 2.0
val py2R = (py1C + py1R) / 2.0
it.color = firstOrderSlopeCol
drawLineOnWorld(px1L, py1L, px1C, py1C)
drawLineOnWorld(px1C, py1C, px1R, py1R)
/*(1..cameraNodes.lastIndex + 16).forEach { index0 ->
val x1 = (index0 - 1) * cameraNodeWidth * TILE_SIZEF; val x2 = (index0 - 0) * cameraNodeWidth * TILE_SIZEF
val y1 = cameraNodes[(index0 - 1) fmod cameraNodes.size]; val y2 = cameraNodes[index0 fmod cameraNodes.size]
drawLineOnWorld(x1, y1, x2, y2)
}*/
it.color = baseSlopeCol
val points = (0..App.scr.width).map { x ->
val worldX = (WorldCamera.x + x).toDouble()
worldX to getPointAt(worldX)
}
points.forEachIndexed { index, (x, y) ->
if (index > 0) {
drawLineOnWorld(points[index - 1].first, points[index - 1].second, x, y)
}
}
it.color = secondOrderSlopeCol
drawLineOnWorld(px2L, py2L, px2R, py2R)
}
}
}
else {
printdbgerr(this, "Demoworld is already been destroyed")
@@ -332,16 +429,34 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
batch.color = Color.WHITE
if (App.is32BitJVM && uiRemoCon.currentRemoConContents.parent == null) {
// if (uiRemoCon.currentRemoConContents.parent == null) {
val linegap = 4
val imgTxtGap = 10
val yoff = App.scr.height - App.scr.tvSafeGraphicsHeight - 64 - (3*(20+linegap)) - imgTxtGap - 9
Toolkit.drawCentered(batch, warning32bitJavaIcon, yoff)
for (i in 0..2) {
val text = Lang.get("GAME_32BIT_WARNING${i+1}", (i != 2))
if (i == 2) batch.color = Toolkit.Theme.COL_SELECTED
App.fontGame.draw(batch, text, ((drawWidth - App.fontGame.getWidth(text)) / 2).toFloat(), yoff + imgTxtGap + 64f + linegap + i*(20+linegap))
// warn: 32-bit
val linegap = 4
val imgTxtGap = 10
val yoff = App.scr.height - App.scr.tvSafeGraphicsHeight - 64 - (3*(20+linegap)) - imgTxtGap - 9
if (uiRemoCon.currentRemoConContents.parent == null) {
var texts = emptyList<String>()
var textcols = emptyList<Color>()
if (App.is32BitJVM) {
Toolkit.drawCentered(batch, warning32bitJavaIcon, yoff)
texts = (1..3).map { Lang.get("GAME_32BIT_WARNING$it", (it != 3)) }
textcols = (1..3).map { if (it == 3) Toolkit.Theme.COL_SELECTED else Color.WHITE }
}
// warn: rosetta on Apple M-chips
else if (App.getUndesirableConditions() == "apple_execution_through_rosetta") {
Toolkit.drawCentered(batch, warningAppleRosettaIcon, yoff)
texts = (1..2).map { Lang.get("GAME_APPLE_ROSETTA_WARNING$it") }
textcols = texts.map { Color.WHITE }
}
texts.forEachIndexed { i, text ->
batch.color = textcols[i]
App.fontGame.draw(
batch,
text,
((drawWidth - App.fontGame.getWidth(text)) / 2).toFloat(),
yoff + imgTxtGap + 64f + linegap + i * (20 + linegap)
)
}
}
}
@@ -379,6 +494,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
uiRemoCon.dispose()
demoWorld.dispose()
warning32bitJavaIcon.texture.dispose()
warningAppleRosettaIcon.texture.dispose()
}
override fun inputStrobed(e: TerrarumKeyboardEvent) {
@@ -435,7 +551,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
override val hitbox = Hitbox(0.0, 0.0, 2.0, 2.0)
init {
actorValue[AVKey.SPEED] = 1.666
actorValue[AVKey.SPEED] = 1.666 * (if (Math.random() < 1.0 / 65536.0) -1 else 1) // some easter egg
hitbox.setPosition(
HQRNG().nextInt(demoWorld.width) * TILE_SIZED,
0.0 // Y pos: placeholder; camera AI will take it over
@@ -475,7 +591,14 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
}
var targetBearing = 0.0
var currentBearing = Double.NaN
var currentBearing = 0.0
var bearing1A = 0.0
var bearing1B = 0.0
var bearing1C = 0.0
var bearing2A = 0.0
var bearing2B = 0.0
var bearing3A = 0.0
override fun moveTo(bearing: Double) {
targetBearing = bearing
@@ -495,6 +618,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
val xdiff = if (xdiff1 < 0) xdiff2 else xdiff1
val ydiff = toY - hitbox.canonicalY
moveTo(atan2(ydiff, xdiff))
hitbox.setPositionX(hitbox.canonicalX % ww)
}

View File

@@ -14,11 +14,111 @@ import org.dyn4j.geometry.Vector2
import java.util.*
typealias BlockBoxIndex = Int
typealias WireEmissionType = String
interface Electric {
val wireEmitterTypes: HashMap<String, BlockBoxIndex>
val wireEmission: HashMap<BlockBoxIndex, Vector2>
val wireConsumption: HashMap<BlockBoxIndex, Vector2>
open class Electric : FixtureBase {
protected constructor() : super() {
oldSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() }
}
/**
* Making the sprite: do not address the CommonResourcePool directly; just do it like this snippet:
*
* ```makeNewSprite(FixtureBase.getSpritesheet("basegame", "sprites/fixtures/tiki_torch.tga", 16, 32))```
*/
constructor(
blockBox0: BlockBox,
blockBoxProps: BlockBoxProps = BlockBoxProps(0),
renderOrder: RenderOrder = RenderOrder.MIDDLE,
nameFun: () -> String,
mainUI: UICanvas? = null,
inventory: FixtureInventory? = null,
id: ActorID? = null
) : super(renderOrder, PhysProperties.IMMOBILE, id) {
blockBox = blockBox0
setHitboxDimension(TILE_SIZE * blockBox.width, TILE_SIZE * blockBox.height, 0, 0)
this.blockBoxProps = blockBoxProps
this.renderOrder = renderOrder
this.nameFun = nameFun
this.mainUI = mainUI
this.inventory = inventory
if (mainUI != null)
App.disposables.add(mainUI)
oldSinkStatus = Array(blockBox.width * blockBox.height) { Vector2() }
}
companion object {
const val ELECTIC_THRESHOLD_HIGH = 0.9
const val ELECTRIC_THRESHOLD_LOW = 0.1
const val ELECTRIC_THRESHOLD_EDGE_DELTA = 0.7
}
fun getWireEmitterAt(point: Point2i) = this.wireEmitterTypes[pointToBlockBoxIndex(point)]
fun getWireEmitterAt(x: Int, y: Int) = this.wireEmitterTypes[pointToBlockBoxIndex(x, y)]
fun getWireSinkAt(point: Point2i) = this.wireSinkTypes[pointToBlockBoxIndex(point)]
fun getWireSinkAt(x: Int, y: Int) = this.wireSinkTypes[pointToBlockBoxIndex(x, y)]
fun setWireEmitterAt(x: Int, y: Int, type: WireEmissionType) { wireEmitterTypes[pointToBlockBoxIndex(x, y)] = type }
fun setWireSinkAt(x: Int, y: Int, type: WireEmissionType) { wireSinkTypes[pointToBlockBoxIndex(x, y)] = type }
fun setWireEmissionAt(x: Int, y: Int, emission: Vector2) { wireEmission[pointToBlockBoxIndex(x, y)] = emission }
fun setWireConsumptionAt(x: Int, y: Int, consumption: Vector2) { wireConsumption[pointToBlockBoxIndex(x, y)] = consumption }
@Transient val wireEmitterTypes: HashMap<BlockBoxIndex, WireEmissionType> = HashMap()
@Transient val wireSinkTypes: HashMap<BlockBoxIndex, WireEmissionType> = HashMap()
@Transient val wireEmission: HashMap<BlockBoxIndex, Vector2> = HashMap()
@Transient val wireConsumption: HashMap<BlockBoxIndex, Vector2> = HashMap()
/** Triggered when 'digital_bit' rises from low to high. Edge detection only considers the real component (labeled as 'x') of the vector */
open fun onRisingEdge(readFrom: BlockBoxIndex) {}
/** Triggered when 'digital_bit' rises from high to low. Edge detection only considers the real component (labeled as 'x') of the vector */
open fun onFallingEdge(readFrom: BlockBoxIndex) {}
/** Triggered when 'digital_bit' is held high. This function WILL NOT be triggered simultaneously with the rising edge. Level detection only considers the real component (labeled as 'x') of the vector */
open fun onSignalHigh(readFrom: BlockBoxIndex) {}
/** Triggered when 'digital_bit' is held low. This function WILL NOT be triggered simultaneously with the falling edge. Level detection only considers the real component (labeled as 'x') of the vector */
open fun onSignalLow(readFrom: BlockBoxIndex) {}
private val oldSinkStatus: Array<Vector2>
open fun updateOnWireGraphTraversal(offsetX: Int, offsetY: Int, sinkType: WireEmissionType) {
val index = pointToBlockBoxIndex(offsetX, offsetY)
val old = oldSinkStatus[index]
val wx = offsetX + intTilewiseHitbox.startX.toInt()
val wy = offsetY + intTilewiseHitbox.startY.toInt()
val new = WireCodex.getAllWiresThatAccepts("digital_bit").fold(Vector2()) { acc, (id, _) ->
INGAME.world.getWireEmitStateOf(wx, wy, id).let {
Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0))
}
}
if (sinkType == "digital_bit") {
if (new.x - old.x >= ELECTRIC_THRESHOLD_EDGE_DELTA && new.x >= ELECTIC_THRESHOLD_HIGH)
onRisingEdge(index)
else if (old.x - new.x >= ELECTRIC_THRESHOLD_EDGE_DELTA && new.x <= ELECTRIC_THRESHOLD_LOW)
onFallingEdge(index)
else if (new.x >= ELECTIC_THRESHOLD_HIGH)
onSignalHigh(index)
else if (new.y <= ELECTRIC_THRESHOLD_LOW)
onSignalLow(index)
}
}
override fun update(delta: Float) {
super.update(delta)
oldSinkStatus.indices.forEach { index ->
val wx = (index % blockBox.width) + intTilewiseHitbox.startX.toInt()
val wy = (index / blockBox.width) + intTilewiseHitbox.startY.toInt()
val new = WireCodex.getAllWiresThatAccepts(getWireSinkAt(index % blockBox.width, index / blockBox.width) ?: "").fold(Vector2()) { acc, (id, _) ->
INGAME.world.getWireEmitStateOf(wx, wy, id).let {
Vector2(acc.x + (it?.x ?: 0.0), acc.y + (it?.y ?: 0.0))
}
}
oldSinkStatus[index].set(new)
}
}
}
/**
@@ -35,15 +135,20 @@ open class FixtureBase : ActorWithBody, CuedByTerrainChange {
protected set
lateinit var blockBox: BlockBox // something like TapestryObject will want to redefine this
fun blockBoxIndexToPoint2i(it: BlockBoxIndex): Point2i = this.blockBox.width.let { w -> Point2i(it % w, it / w) }
var blockBoxProps: BlockBoxProps = BlockBoxProps(0)
fun pointToBlockBoxIndex(point: Point2i) = point.y * this.blockBox.width + point.x
fun pointToBlockBoxIndex(x: Int, y: Int) = y * this.blockBox.width + x
@Transient var blockBoxProps: BlockBoxProps = BlockBoxProps(0)
@Transient var nameFun: () -> String = { "" }
@Transient var mainUI: UICanvas? = null
var inventory: FixtureInventory? = null
protected var actorThatInstalledThisFixture: UUID? = null
private constructor() : super(RenderOrder.BEHIND, PhysProperties.IMMOBILE, null)
protected constructor() : super(RenderOrder.BEHIND, PhysProperties.IMMOBILE, null)
protected constructor(renderOrder: RenderOrder, physProp: PhysProperties, id: ActorID?) : super(renderOrder, physProp, id)
/**
@@ -357,10 +462,10 @@ interface CuedByWireChange {
* Standard 32-bit binary flags.
*
* (LSB)
* - 0: fluid resist - when FALSE, the fixture will break itself to item/nothing.
* For example, crops has this flag FALSE.
* - 1: don't drop item when broken - when TRUE, the fixture will simply disappear instead of
* dropping itself. For example, crop has this flag TRUE.
* - 0: fluid intolerance - when SET, the fixture will break itself to item/nothing (depends on the flag #1).
* For example, crops have this flag SET.
* - 1: no drops - when SET, the fixture will simply disappear instead of dropping itself.
* For example, crops have this flag SET.
*
* (MSB)
*

View File

@@ -1,9 +1,5 @@
package net.torvald.terrarum.modulebasegame.gameactors
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.langpack.Lang
@@ -11,12 +7,7 @@ import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import org.dyn4j.geometry.Vector2
class FixtureLogicSignalEmitter : FixtureBase, Electric {
override val wireEmitterTypes: HashMap<String, BlockBoxIndex> = HashMap()
override val wireEmission: HashMap<BlockBoxIndex, Vector2> = HashMap()
override val wireConsumption: HashMap<BlockBoxIndex, Vector2> = HashMap()
class FixtureLogicSignalEmitter : Electric {
constructor() : super(
BlockBox(BlockBox.NO_COLLISION, 1, 1),
@@ -34,14 +25,9 @@ class FixtureLogicSignalEmitter : FixtureBase, Electric {
}
actorValue[AVKey.BASEMASS] = MASS
}
override fun update(delta: Float) {
// the values does not get preserved on save reload??
wireEmitterTypes["digital_bit"] = 0
wireEmission[0] = Vector2(1.0, 0.0)
super.update(delta)
setWireEmitterAt(0, 0, "digital_bit")
setWireEmissionAt(0, 0, Vector2(1.0, 0.0))
}
override fun dispose() { }

View File

@@ -1,22 +1,47 @@
package net.torvald.terrarum.modulebasegame.gameactors
import net.torvald.random.XXHash64
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.WireCodex
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.WorldgenLoadScreen
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory.Companion.CAPACITY_MODE_WEIGHT
import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
import net.torvald.terrarum.modulebasegame.serialise.LoadSavegame
import net.torvald.terrarum.modulebasegame.serialise.ReadActor
import net.torvald.terrarum.modulebasegame.ui.UILoadGovernor
import net.torvald.terrarum.modulebasegame.ui.UIWorldPortal
import net.torvald.terrarum.savegame.ByteArray64Reader
import net.torvald.terrarum.savegame.DiskSkimmer
import net.torvald.terrarum.savegame.VDFileID
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import org.dyn4j.geometry.Vector2
import java.util.HashMap
/**
* Created by minjaesong on 2023-05-28.
*/
class FixtureWorldPortal : FixtureBase {
class FixtureWorldPortal : Electric {
constructor() : super(
BlockBox(BlockBox.NO_COLLISION, 5, 2),
nameFun = { Lang["ITEM_WORLD_PORTAL"] },
mainUI = UIWorldPortal()
mainUI = UIWorldPortal(),
// inventory = FixtureInventory(200, CAPACITY_MODE_WEIGHT)
) {
// TODO do something with (mainUI as UIWorldPortal).***
// (mainUI as UIWorldPortal).let { ui ->
// ui.transitionalCargo.chestInventory = this.inventory!!
// ui.transitionalCargo.chestNameFun = this.nameFun
// }
(mainUI as UIWorldPortal).host = this
}
@@ -30,6 +55,60 @@ class FixtureWorldPortal : FixtureBase {
}
actorValue[AVKey.BASEMASS] = FixtureLogicSignalEmitter.MASS
setWireSinkAt(2, 1, "digital_bit")
}
@Transient internal var teleportRequest: TeleportRequest? = null
override fun update(delta: Float) {
super.update(delta)
}
override fun onRisingEdge(readFrom: BlockBoxIndex) {
printdbg(this, "teleport! $teleportRequest")
teleportRequest?.let {
if (it.worldDiskToLoad != null && it.worldLoadParam != null) {
throw InternalError("Contradiction -- worldDiskToLoad and worldLoadParam are both not null: $teleportRequest")
}
val player = INGAME.actorGamer
// load existing
val jobAfterSave: () -> Unit
if (it.worldDiskToLoad != null) {
UILoadGovernor.worldDisk = it.worldDiskToLoad
UILoadGovernor.playerDisk = App.savegamePlayers[player.uuid]!!.files[0]
jobAfterSave = {
UILoadGovernor.playerDisk!!.rebuild()
LoadSavegame(UILoadGovernor.playerDisk!!, UILoadGovernor.worldDisk!!)
}
}
// create new
else {
jobAfterSave = {
val wx = it.worldLoadParam!!.width
val wy = it.worldLoadParam!!.height
val seed = it.worldLoadParam!!.worldGenSeed
val name = it.worldLoadParam!!.savegameName
printdbg(this, "generate for teleportation! Size=${wx}x${wy}, Name=$name, Seed=$seed")
val ingame = TerrarumIngame(App.batch)
val worldParam = TerrarumIngame.NewGameParams(player, it.worldLoadParam)
ingame.gameLoadInfoPayload = worldParam
ingame.gameLoadMode = TerrarumIngame.GameLoadMode.CREATE_NEW
Terrarum.setCurrentIngameInstance(ingame)
val loadScreen = WorldgenLoadScreen(ingame, wx, wy)
App.setLoadScreen(loadScreen)
}
}
INGAME.requestForceSave(jobAfterSave)
teleportRequest = null
}
}
override fun reload() {
@@ -37,4 +116,9 @@ class FixtureWorldPortal : FixtureBase {
// TODO do something with (mainUI as UIWorldPortal).***
}
internal data class TeleportRequest(
val worldDiskToLoad: DiskSkimmer?, // for loading existing worlds
val worldLoadParam: TerrarumIngame.NewWorldParameters? // for creating new world
)
}

View File

@@ -25,7 +25,7 @@ class PhysTestBall : ActorWithBody(RenderOrder.MIDDLE, PhysProperties.PHYSICS_OB
}
override fun drawBody(batch: SpriteBatch) {
Terrarum.inShapeRenderer {
/*Terrarum.inShapeRenderer {
it.color = color
it.circle(
hitbox.startX.toFloat() - 1f,
@@ -44,7 +44,7 @@ class PhysTestBall : ActorWithBody(RenderOrder.MIDDLE, PhysProperties.PHYSICS_OB
hitbox.startY.toFloat() - 1f,
hitbox.width.toFloat()
)
}
}*/
//println(moveDelta)
}

View File

@@ -68,7 +68,6 @@ open class FixtureItemBase(originalID: ItemID, val fixtureClassName: String) : G
(INGAME as TerrarumIngame).blockMarkingActor.let {
it.setGhost(ghostItem.get())
it.isVisible = true
it.update(delta)
it.setGhostColourBlock()
mouseInInteractableRange(actor) { it.setGhostColourAllow(); 0L }
@@ -80,7 +79,6 @@ open class FixtureItemBase(originalID: ItemID, val fixtureClassName: String) : G
(INGAME as TerrarumIngame).blockMarkingActor.let {
it.unsetGhost()
it.isVisible = false
it.setGhostColourNone()
}
}

View File

@@ -51,7 +51,7 @@ class WireGraphDebugger(originalID: ItemID) : GameItem(originalID) {
val wireName = WireCodex[itemID].nameKey
val emit = simCell.emt
val recv = simCell.rcv
val recv = simCell.rcp
sb.append("$connexionIcon $wireName")
sb.append("\nE: $emit")

View File

@@ -1,35 +1,5 @@
package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.ItemCodex
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.ReferencingRanges.PREFIX_DYNAMICITEM
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.gameactors.Pocketed
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.toInt
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.savegame.VDFileID.LOADORDER
import net.torvald.terrarum.savegame.VDFileID.ROOT
import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO
import net.torvald.terrarum.savegame.VDFileID.THUMBNAIL
import net.torvald.terrarum.serialise.Common
import java.io.File
import java.util.zip.GZIPOutputStream
/**
* Will happily overwrite existing entry
*/
private fun addFile(disk: VirtualDisk, file: DiskEntry) {
disk.entries[file.entryID] = file
file.parentEntryID = 0
val dir = VDUtil.getAsDirectory(disk, 0)
if (!dir.contains(file.entryID)) dir.add(file.entryID)
}
abstract class SavingThread(private val errorHandler: (Throwable) -> Unit) : Runnable {
abstract fun save()
@@ -45,196 +15,4 @@ abstract class SavingThread(private val errorHandler: (Throwable) -> Unit) : Run
}
}
/**
* Created by minjaesong on 2021-09-14.
*/
class WorldSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val hasThumbnail: Boolean,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
override fun save() {
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.WORLD_DATA
if (hasThumbnail) {
while (!IngameRenderer.fboRGBexportedLatch) {
Thread.sleep(1L)
}
}
val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
val playersList: List<IngamePlayer> = allTheActors.filterIsInstance<IngamePlayer>()
val actorsList = allTheActors.filter { WriteWorld.actorAcceptable(it) }
val layers = intArrayOf(0,1).map { ingame.world.getLayer(it) }
val cw = ingame.world.width / LandUtil.CHUNK_W
val ch = ingame.world.height / LandUtil.CHUNK_H
WriteSavegame.saveProgress = 0f
WriteSavegame.saveProgressMax = 3f + (cw * ch * layers.size) + actorsList.size
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
printdbg(this, "Writing metadata...")
val creation_t = ingame.world.creationTime
// Write subset of Ingame.ItemCodex
// The existing ItemCodex must be rewritten to clear out obsolete records
// We're assuming the dynamic item generated by players does exist in the world, and it's recorded
// into the world's dynamicToStaticTable, therefore every item recorded into the world's dynamicToStaticTable
// can be found in this world without need to look up the players
ingame.world.dynamicToStaticTable.clear()
ingame.world.dynamicItemInventory.clear()
actorsList.filterIsInstance<Pocketed>().forEach { actor ->
actor.inventory.forEach { (itemid, _) ->
printdbg(this, "World side dynamicitem: $itemid contained in $actor")
if (itemid.startsWith("$PREFIX_DYNAMICITEM:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
actorsList.filterIsInstance<FixtureBase>().forEach { fixture ->
fixture.inventory?.forEach { (itemid, _) ->
printdbg(this, "World side dynamicitem: $itemid contained in $fixture")
if (itemid.startsWith("$PREFIX_DYNAMICITEM:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
if (hasThumbnail) {
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(THUMBNAIL, ROOT, creation_t, time_t, thumbContent)
addFile(disk, thumb)
}
WriteSavegame.saveProgress += 1f
// Write World //
val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList))
val world = DiskEntry(SAVEGAMEINFO, ROOT, creation_t, time_t, worldMeta)
addFile(disk, world)
WriteSavegame.saveProgress += 1f
for (layer in layers.indices) {
for (cx in 0 until cw) {
for (cy in 0 until ch) {
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong()
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy)
val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber
val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15"
addFile(disk, entry)
WriteSavegame.saveProgress += 1
}
}
}
// Write Actors //
actorsList.forEachIndexed { count, it ->
// Echo("Writing actors... ${count+1}/${actorsList.size}")
val actorContent = EntryFile(WriteActor.encodeToByteArray64(it))
val actor = DiskEntry(it.referenceID.toLong(), ROOT, creation_t, time_t, actorContent)
addFile(disk, actor)
WriteSavegame.saveProgress += 1
}
// write loadorder //
val loadOrderBa64Writer = ByteArray64Writer(Common.CHARSET)
loadOrderBa64Writer.write(ModMgr.loadOrder.joinToString("\n"))
loadOrderBa64Writer.flush(); loadOrderBa64Writer.close()
val loadOrderText = loadOrderBa64Writer.toByteArray64()
val loadOrderContents = EntryFile(loadOrderText)
addFile(disk, DiskEntry(LOADORDER, ROOT, creation_t, time_t, loadOrderContents))
// Echo("Writing file to disk...")
disk.entries[0]!!.modificationDate = time_t
// entry zero MUST NOT be used to get lastPlayDate, but we'll update it anyway
// use entry -1 for that purpose!
disk.capacity = 0
VDUtil.dumpToRealMachine(disk, outFile)
printdbg(this, "Game saved with size of ${outFile.length()} bytes")
if (hasThumbnail) IngameRenderer.fboRGBexportedLatch = false
WriteSavegame.savingStatus = 255
callback()
}
}
/**
* This function called means the "Avatar" was not externally created and thus has no sprite-bodypart-name-to-entry-number-map
*
* Created by minjaesong on 2021-10-08
*/
class PlayerSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val hasThumbnail: Boolean,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
override fun save() {
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.PLAYER_DATA
disk.capacity = 0L
WriteSavegame.saveProgress = 0f
printdbg(this, "Writing The Player...")
WritePlayer(ingame.actorGamer, disk, ingame, time_t)
disk.entries[0]!!.modificationDate = time_t
VDUtil.dumpToRealMachine(disk, outFile)
callback()
}
}
const val SCREENCAP_WAIT_TRY_MAX = 256

View File

@@ -0,0 +1,78 @@
package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.App
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.toInt
import java.io.File
import java.util.zip.GZIPOutputStream
/**
* This function called means the "Avatar" was not externally created and thus has no sprite-bodypart-name-to-entry-number-map
*
* Created by minjaesong on 2021-10-08
*/
class PlayerSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
/**
* Will happily overwrite existing entry
*/
private fun addFile(disk: VirtualDisk, file: DiskEntry) {
disk.entries[file.entryID] = file
file.parentEntryID = 0
val dir = VDUtil.getAsDirectory(disk, 0)
if (!dir.contains(file.entryID)) dir.add(file.entryID)
}
override fun save() {
App.printdbg(this, "outFile: ${outFile.path}")
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.PLAYER_DATA
disk.capacity = 0L
WriteSavegame.saveProgress = 0f
// wait for screencap
var emergencyStopCnt = 0
while (IngameRenderer.screencapBusy) {
// printdbg(this, "spinning for screencap to be taken")
Thread.sleep(4L)
emergencyStopCnt += 1
if (emergencyStopCnt >= SCREENCAP_WAIT_TRY_MAX) throw InterruptedException("Waiting screencap to be taken for too long")
}
// write screencap
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
// App.disposables.add(IngameRenderer.fboRGBexport)
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb =
DiskEntry(VDFileID.PLAYER_SCREENSHOT, VDFileID.ROOT, ingame.world.creationTime, time_t, thumbContent)
addFile(disk, thumb)
App.printdbg(this, "Writing The Player...")
WritePlayer(ingame.actorGamer, disk, ingame, time_t)
disk.entries[0]!!.modificationDate = time_t
VDUtil.dumpToRealMachine(disk, outFile)
// IngameRenderer.screencapBusy = false
callback()
}
}

View File

@@ -24,7 +24,6 @@ class QuickSingleplayerWorldSavingThread(
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val hasThumbnail: Boolean,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
@@ -46,12 +45,17 @@ class QuickSingleplayerWorldSavingThread(
override fun save() {
printdbg(this, "outFile: ${outFile.path}")
val skimmer = DiskSkimmer(outFile)
if (hasThumbnail) {
while (!IngameRenderer.fboRGBexportedLatch) {
Thread.sleep(1L)
}
// wait for screencap
var emergencyStopCnt = 0
while (IngameRenderer.screencapBusy) {
// printdbg(this, "spinning for screencap to be taken")
Thread.sleep(4L)
emergencyStopCnt += 1
if (emergencyStopCnt >= SCREENCAP_WAIT_TRY_MAX) throw InterruptedException("Waiting screencap to be taken for too long")
}
val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
@@ -76,14 +80,14 @@ class QuickSingleplayerWorldSavingThread(
val creation_t = ingame.world.creationTime
if (hasThumbnail) {
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(THUMBNAIL, ROOT, creation_t, time_t, thumbContent)
addFile(disk, thumb)
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(THUMBNAIL, ROOT, creation_t, time_t, thumbContent)
addFile(disk, thumb)
}
WriteSavegame.saveProgress += 1f
@@ -150,7 +154,7 @@ class QuickSingleplayerWorldSavingThread(
printdbg(this, "Game saved with size of ${outFile.length()} bytes")
if (hasThumbnail) IngameRenderer.fboRGBexportedLatch = false
// IngameRenderer.screencapBusy = false
WriteSavegame.savingStatus = 255
ingame.clearModifiedChunks()

View File

@@ -0,0 +1,189 @@
package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.*
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.gameactors.Pocketed
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.serialise.Common
import java.io.File
import java.util.zip.GZIPOutputStream
/**
* Created by minjaesong on 2021-09-14.
*/
class WorldSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
/**
* Will happily overwrite existing entry
*/
private fun addFile(disk: VirtualDisk, file: DiskEntry) {
disk.entries[file.entryID] = file
file.parentEntryID = 0
val dir = VDUtil.getAsDirectory(disk, 0)
if (!dir.contains(file.entryID)) dir.add(file.entryID)
}
override fun save() {
App.printdbg(this, "outFile: ${outFile.path}")
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.WORLD_DATA
// wait for screencap
var emergencyStopCnt = 0
while (IngameRenderer.screencapBusy) {
// printdbg(this, "spinning for screencap to be taken")
Thread.sleep(4L)
emergencyStopCnt += 1
if (emergencyStopCnt >= SCREENCAP_WAIT_TRY_MAX) throw InterruptedException("Waiting screencap to be taken for too long")
}
val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
val playersList: List<IngamePlayer> = allTheActors.filterIsInstance<IngamePlayer>()
val actorsList = allTheActors.filter { WriteWorld.actorAcceptable(it) }
val layers = intArrayOf(0,1).map { ingame.world.getLayer(it) }
val cw = ingame.world.width / LandUtil.CHUNK_W
val ch = ingame.world.height / LandUtil.CHUNK_H
WriteSavegame.saveProgress = 0f
WriteSavegame.saveProgressMax = 3f + (cw * ch * layers.size) + actorsList.size
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
App.printdbg(this, "Writing metadata...")
val creation_t = ingame.world.creationTime
// Write subset of Ingame.ItemCodex
// The existing ItemCodex must be rewritten to clear out obsolete records
// We're assuming the dynamic item generated by players does exist in the world, and it's recorded
// into the world's dynamicToStaticTable, therefore every item recorded into the world's dynamicToStaticTable
// can be found in this world without need to look up the players
ingame.world.dynamicToStaticTable.clear()
ingame.world.dynamicItemInventory.clear()
actorsList.filterIsInstance<Pocketed>().forEach { actor ->
actor.inventory.forEach { (itemid, _) ->
App.printdbg(this, "World side dynamicitem: $itemid contained in $actor")
if (itemid.startsWith("${ReferencingRanges.PREFIX_DYNAMICITEM}:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
actorsList.filterIsInstance<FixtureBase>().forEach { fixture ->
fixture.inventory?.forEach { (itemid, _) ->
App.printdbg(this, "World side dynamicitem: $itemid contained in $fixture")
if (itemid.startsWith("${ReferencingRanges.PREFIX_DYNAMICITEM}:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(VDFileID.THUMBNAIL, VDFileID.ROOT, creation_t, time_t, thumbContent)
addFile(disk, thumb)
WriteSavegame.saveProgress += 1f
// Write World //
val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList))
val world = DiskEntry(VDFileID.SAVEGAMEINFO, VDFileID.ROOT, creation_t, time_t, worldMeta)
addFile(disk, world)
WriteSavegame.saveProgress += 1f
for (layer in layers.indices) {
for (cx in 0 until cw) {
for (cy in 0 until ch) {
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong()
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy)
val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber
val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15"
addFile(disk, entry)
WriteSavegame.saveProgress += 1
}
}
}
// Write Actors //
actorsList.forEachIndexed { count, it ->
// Echo("Writing actors... ${count+1}/${actorsList.size}")
val actorContent = EntryFile(WriteActor.encodeToByteArray64(it))
val actor = DiskEntry(it.referenceID.toLong(), VDFileID.ROOT, creation_t, time_t, actorContent)
addFile(disk, actor)
WriteSavegame.saveProgress += 1
}
// write loadorder //
val loadOrderBa64Writer = ByteArray64Writer(Common.CHARSET)
loadOrderBa64Writer.write(ModMgr.loadOrder.joinToString("\n"))
loadOrderBa64Writer.flush(); loadOrderBa64Writer.close()
val loadOrderText = loadOrderBa64Writer.toByteArray64()
val loadOrderContents = EntryFile(loadOrderText)
addFile(disk, DiskEntry(VDFileID.LOADORDER, VDFileID.ROOT, creation_t, time_t, loadOrderContents))
// Echo("Writing file to disk...")
disk.entries[0]!!.modificationDate = time_t
// entry zero MUST NOT be used to get lastPlayDate, but we'll update it anyway
// use entry -1 for that purpose!
disk.capacity = 0
VDUtil.dumpToRealMachine(disk, outFile)
App.printdbg(this, "Game saved with size of ${outFile.length()} bytes")
// IngameRenderer.screencapBusy = false
WriteSavegame.savingStatus = 255
callback()
}
}

View File

@@ -151,8 +151,16 @@ object ReadPlayer {
object ReadActor {
operator fun invoke(disk: SimpleFileSystem, dataStream: Reader): Actor =
Common.jsoner.fromJson<Actor?>(null, dataStream).also {
fillInDetails(disk, it)
try {
Common.jsoner.fromJson<Actor?>(null, dataStream).also {
fillInDetails(disk, it)
}
}
catch (e: ClassCastException) {
System.err.println(e.message)
System.err.println("The JSON:")
System.err.println(dataStream.readText())
throw e
}
private fun fillInDetails(disk: SimpleFileSystem, actor: Actor) {

View File

@@ -36,43 +36,45 @@ object WriteSavegame {
@Volatile var saveProgress = 0f
@Volatile var saveProgressMax = 1f
private fun getSaveThread(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, hasThumbnail: Boolean, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) = when (mode) {
SaveMode.WORLD -> WorldSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback, errorHandler)
SaveMode.PLAYER -> PlayerSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback, errorHandler)
SaveMode.QUICK_WORLD -> QuickSingleplayerWorldSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback, errorHandler)
private fun getSaveThread(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) = when (mode) {
SaveMode.WORLD -> WorldSavingThread(time_t, disk, outFile, ingame, isAuto, callback, errorHandler)
SaveMode.PLAYER -> PlayerSavingThread(time_t, disk, outFile, ingame, isAuto, callback, errorHandler)
SaveMode.QUICK_WORLD -> QuickSingleplayerWorldSavingThread(time_t, disk, outFile, ingame, isAuto, callback, errorHandler)
else -> throw IllegalArgumentException("$mode")
}
private fun installScreencap() {
IngameRenderer.screencapExportCallback = { fb ->
printdbg(this, "Generating thumbnail...")
val w = 960
val h = 640
val cx = /*1-*/(WorldCamera.x % 2)
val cy = /*1-*/(WorldCamera.y % 2)
val x = (fb.width - w) / 2 - cx // force the even-numbered position
val y = (fb.height - h) / 2 - cy // force the even-numbered position
val p = Pixmap.createFromFrameBuffer(x, y, w, h)
IngameRenderer.fboRGBexport = p
//PixmapIO2._writeTGA(gzout, p, true, true)
//p.dispose()
printdbg(this, "Done thumbnail generation")
}
}
operator fun invoke(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0
val hasThumbnail = (mode == SaveMode.WORLD || mode == SaveMode.QUICK_WORLD)
printdbg(this, "Save queued")
if (hasThumbnail) {
IngameRenderer.screencapExportCallback = { fb ->
printdbg(this, "Generating thumbnail...")
installScreencap()
try { printdbg(this, "ScreencapExport installed: ${IngameRenderer.screencapExportCallback}") }
catch (e: UninitializedPropertyAccessException) { printdbg(this, "ScreencapExport installed: no") }
IngameRenderer.requestScreencap()
val w = 960
val h = 640
val cx = /*1-*/(WorldCamera.x % 2)
val cy = /*1-*/(WorldCamera.y % 2)
val x = (fb.width - w) / 2 - cx // force the even-numbered position
val y = (fb.height - h) / 2 - cy // force the even-numbered position
val p = Pixmap.createFromFrameBuffer(x, y, w, h)
IngameRenderer.fboRGBexport = p
//PixmapIO2._writeTGA(gzout, p, true, true)
//p.dispose()
IngameRenderer.fboRGBexportedLatch = true
printdbg(this, "Done thumbnail generation")
}
IngameRenderer.screencapRequested = true
}
val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, hasThumbnail, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
savingThread.start()
// it is caller's job to keep the game paused or keep a "save in progress" ui up
@@ -81,12 +83,15 @@ object WriteSavegame {
fun immediate(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0
printdbg(this, "Immediate save fired")
val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, false, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
installScreencap()
try { printdbg(this, "ScreencapExport installed: ${IngameRenderer.screencapExportCallback}") }
catch (e: UninitializedPropertyAccessException) { printdbg(this, "ScreencapExport installed: no") }
IngameRenderer.requestScreencap()
val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
savingThread.start()
// it is caller's job to keep the game paused or keep a "save in progress" ui up
@@ -126,7 +131,7 @@ object LoadSavegame {
printdbg(this, "Player localhash: ${player.localHashStr}, hasSprite: ${player.sprite != null}")
val currentWorldId = player.worldCurrentlyPlaying
val worldDisk = worldDisk0 ?: App.savegameWorlds[currentWorldId]!!
val worldDisk = worldDisk0 ?: App.savegameWorlds[currentWorldId]!!.loadable()
val world = ReadWorld(ByteArray64Reader(worldDisk.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET), worldDisk.diskFile)
world.layerTerrain = BlockLayer(world.width, world.height)

View File

@@ -48,12 +48,9 @@ class Notification : UICanvas() {
}
}
private val drawColor = Color(1f, 1f, 1f, 1f)
override fun renderUI(batch: SpriteBatch, camera: Camera) {
blendNormalStraightAlpha(batch)
drawColor.a = handler.opacity
fontCol.a = handler.opacity
fontCol.a = handler.opacity * OPACITY
val realTextWidth = 12 + if (message.size == 1)
App.fontGame.getWidth(message[0])
@@ -63,12 +60,11 @@ class Notification : UICanvas() {
// force the UI to the centre of the screen
this.posX = (App.scr.width - displayedTextWidth) / 2
val textHeight = message.size * App.fontGame.lineHeight
batch.color = drawColor
Toolkit.drawBaloon(batch, 0f, -textHeight, displayedTextWidth.toFloat(), textHeight)
Toolkit.drawBaloon(batch, 0f, -textHeight, displayedTextWidth.toFloat(), textHeight, handler.opacity * OPACITY)
batch.color = fontCol
message.forEachIndexed { index, s ->
@@ -79,7 +75,6 @@ class Notification : UICanvas() {
// dunno why, it doesn't work without this.
drawColor.a = 1f
fontCol.a = 1f
}
@@ -111,6 +106,7 @@ class Notification : UICanvas() {
companion object {
// private int messagesShowingIndex = 0;
val OPEN_CLOSE_TIME = 0.16f
const val OPEN_CLOSE_TIME = 0.16f
const val OPACITY = 0.9f
}
}

View File

@@ -66,7 +66,7 @@ class UIBuildingMakerBlockChooser(val parent: BuildingMaker): UICanvas() {
activeCol = Color.WHITE
)
paletteItem.clickOnceListener = { _, _, _ ->
paletteItem.clickOnceListener = { _, _ ->
parent.setPencilColour(prop.id)
}

View File

@@ -100,11 +100,9 @@ class UIBuildingMakerPenMenu(val parent: BuildingMaker): UICanvas() {
toolButtons.forEachIndexed { index, button ->
uiItems.add(button)
button.clickOnceListener = { _, _, b ->
if (b == App.getConfigInt("config_mouseprimary")) {
toolButtonsJob[index].invoke()
closeGracefully()
}
button.clickOnceListener = { _, _ ->
toolButtonsJob[index].invoke()
closeGracefully()
}
}
}

View File

@@ -14,7 +14,6 @@ import net.torvald.terrarum.itemproperties.CraftingCodex
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory
import net.torvald.terrarum.modulebasegame.gameactors.InventoryPair
import net.torvald.terrarum.modulebasegame.ui.*
import net.torvald.terrarum.modulebasegame.ui.UIItemInventoryItemGrid.Companion.listGap
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
@@ -203,8 +202,8 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
)
// make sure grid buttons for ingredients do nothing (even if they are hidden!)
itemListIngredients.gridModeButtons[0].touchDownListener = { _,_,_,_ -> }
itemListIngredients.gridModeButtons[1].touchDownListener = { _,_,_,_ -> }
itemListIngredients.navRemoCon.listButtonListener = { _,_, -> }
itemListIngredients.navRemoCon.gridButtonListener = { _,_, -> }
itemListIngredients.isCompactMode = true
itemListIngredients.setCustomHighlightRuleSub {
it.item?.let { ingredient ->
@@ -252,8 +251,8 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
} } }
)
// make grid mode buttons work together
// itemListPlayer.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) }
// itemListPlayer.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) }
// itemListPlayer.gridModeButtons[0].clickOnceListener = { _,_ -> setCompact(false) }
// itemListPlayer.gridModeButtons[1].clickOnceListener = { _,_ -> setCompact(true) }
@@ -317,7 +316,7 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
}
buttonCraft.touchDownListener = { _,_,_,_ ->
buttonCraft.clickOnceListener = { _,_ ->
getPlayerInventory().let { player -> recipeClicked?.let { recipe ->
// check if player has enough amount of ingredients
val itemCraftable = itemListIngredients.getInventory().itemList.all { (itm, qty) ->
@@ -340,8 +339,8 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
refreshCraftButtonStatus()
}
// make grid mode buttons work together
// itemListCraftable.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) }
// itemListCraftable.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) }
// itemListCraftable.gridModeButtons[0].clickOnceListener = { _,_ -> setCompact(false) }
// itemListCraftable.gridModeButtons[1].clickOnceListener = { _,_ -> setCompact(true) }
handler.allowESCtoClose = true
@@ -387,7 +386,7 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
}
}
buttonCraft.isActive = itemCraftable
buttonCraft.isEnabled = itemCraftable
}
// reset whatever player has selected to null and bring UI to its initial state

View File

@@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.ceilInt
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.CELL_COL
@@ -29,7 +30,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
private val h1MarginBottom = 4
private val options = arrayOf(
arrayOf("", { Lang["MENU_OPTIONS_PERFORMANCE"] }, "h1"),
arrayOf("", { Lang["CREDITS_VFX"] }, "h1"),
arrayOf("fx_dither", { Lang["MENU_OPTIONS_DITHER"] }, "toggle"),
arrayOf("fx_backgroundblur", { Lang["MENU_OPTIONS_BLUR"] }, "toggle"),
arrayOf("maxparticles", { Lang["MENU_OPTIONS_PARTICLES"] }, "spinner,256,1024,256"),
@@ -39,7 +40,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
arrayOf("displayfps", { Lang["MENU_LABEL_FRAMESPERSEC"] }, "spinner,0,300,2"),
arrayOf("usevsync", { Lang["MENU_OPTIONS_VSYNC"] }, "toggle"),
arrayOf("", { "(${Lang["MENU_LABEL_RESTART_REQUIRED"]})" }, "p"),
arrayOf("", { Lang["GAME_GENRE_MISC"] }, "h1"),
arrayOf("", { Lang["MENU_LABEL_STREAMING"] }, "h1"),
arrayOf("fx_streamerslayout", { Lang["MENU_OPTION_STREAMERS_LAYOUT"] }, "toggle"),
)
@@ -90,7 +91,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
}
else if (args.startsWith("toggle")) {
UIItemToggleButton(this, x, y, spinnerWidth, App.getConfigBoolean(optionName)) to { it: UIItem, optionStr: String ->
(it as UIItemToggleButton).clickOnceListener = { _, _, _ ->
(it as UIItemToggleButton).clickOnceListener = { _, _ ->
it.toggle()
App.setConfig(optionStr, it.getStatus())
}
@@ -109,11 +110,16 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
UIItemSpinner(this, x, y, App.getConfigDouble(optionName), arg[1].toDouble(), arg[2].toDouble(), arg[3].toDouble(), spinnerWidth, numberToTextFunction = { "${((it as Double)*100).toInt()}%" }) to { it: UIItem, optionStr: String ->
(it as UIItemSpinner).selectionChangeListener = {
App.setConfig(optionStr, it)
} }
}
}
}
else if (args.startsWith("typeinint")) {
// val arg = args.split(',') // args: none
UIItemTextLineInput(this, x, y, spinnerWidth, { "${App.getConfigInt(optionName)}" }, InputLenCap(4, InputLenCap.CharLenUnit.CODEPOINTS), { it.headkey in Input.Keys.NUM_0..Input.Keys.NUM_9 || it.headkey == Input.Keys.BACKSPACE }) to { it: UIItem, optionStr: String ->
UIItemTextLineInput(this, x, y, spinnerWidth,
defaultValue = { "${App.getConfigInt(optionName)}" },
maxLen = InputLenCap(4, InputLenCap.CharLenUnit.CODEPOINTS),
keyFilter = { it.headkey in Input.Keys.NUM_0..Input.Keys.NUM_9 || it.headkey == Input.Keys.BACKSPACE }
) to { it: UIItem, optionStr: String ->
(it as UIItemTextLineInput).textCommitListener = {
App.setConfig(optionStr, it.toInt()) // HAXXX!!!
}
@@ -122,7 +128,12 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
else if (args.startsWith("typeinres")) {
val keyWidth = optionName.substringBefore(',')
val keyHeight = optionName.substringAfter(',')
UIItemTextLineInput(this, x, y, spinnerWidth, { "${App.getConfigInt(keyWidth)}x${App.getConfigInt(keyHeight)}" }, InputLenCap(9, InputLenCap.CharLenUnit.CODEPOINTS), { it.headkey == Input.Keys.ENTER || it.headkey == Input.Keys.BACKSPACE || it.character?.matches(Regex("[0-9xX]")) == true }, UIItemTextButton.Companion.Alignment.CENTRE) to { it: UIItem, optionStr: String ->
UIItemTextLineInput(this, x, y, spinnerWidth,
defaultValue = { "${App.getConfigInt(keyWidth)}x${App.getConfigInt(keyHeight)}" },
maxLen = InputLenCap(9, InputLenCap.CharLenUnit.CODEPOINTS),
keyFilter = { it.headkey == Input.Keys.ENTER || it.headkey == Input.Keys.BACKSPACE || it.character?.matches(Regex("[0-9xX]")) == true },
alignment = UIItemTextButton.Companion.Alignment.CENTRE
) to { it: UIItem, optionStr: String ->
(it as UIItemTextLineInput).textCommitListener = { text ->
val text = text.lowercase()
if (text.matches(Regex("""[0-9]+x[0-9]+"""))) {
@@ -167,8 +178,11 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
options.forEachIndexed { index, strings ->
val mode = strings[2]
val font = if (mode == "h1") App.fontUITitle else App.fontGame
val label = (strings[1] as () -> String).invoke()
val labelWidth = App.fontGame.getWidth(label)
val labelWidth = font.getWidth(label)
batch.color = when (mode) {
"h1" -> Toolkit.Theme.COL_MOUSE_UP
"p" -> Color.LIGHT_GRAY
@@ -180,7 +194,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
else
drawX + width/2 - panelgap - labelWidth // right aligned at the middle of the panel, offsetted by panelgap
App.fontGame.draw(batch, label, xpos.toFloat(), drawY + optionsYpos[index] - 2f)
font.draw(batch, label, xpos.toFloat(), drawY + optionsYpos[index] - 2f)
// draw hrule
if (mode == "h1") {
@@ -209,6 +223,14 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
}
}
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
return super.touchDown(screenX, screenY, pointer, button)
}
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
return super.touchUp(screenX, screenY, pointer, button)
}
override fun dispose() {
}
}

View File

@@ -110,14 +110,14 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
private val selDrawX = (Toolkit.drawWidth - selectorWidth) / 2
private val halfw = width / 2
private val y1 = 400
private val y2 = y1 + 40
// private val y1 = 400
// private val y2 = y1 + 40
private val lowLayerCodes = IME.getAllLowLayers().sorted()
private val lowLayerNames = lowLayerCodes.map { { IME.getLowLayerByName(it).name } }
private val keyboardLayoutSelection = UIItemTextSelector(this,
selDrawX + (halfselw - textSelWidth) / 2,
y2,
kby + 260,
lowLayerNames,
lowLayerCodes.linearSearch { it == App.getConfigString("basekeyboardlayout") } ?: throw IME.LayoutNotFound(App.getConfigString("basekeyboardlayout")),
textSelWidth
@@ -127,8 +127,8 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
private val imeCodes = listOf("none") + imeCodes0
private val imeNames = listOf({"$EMDASH"}) + imeCodes0.map { { IME.getHighLayerByName(it).name } }
private val imeSelection = UIItemTextSelector(this,
selDrawX + halfselw + (halfselw - textSelWidth) / 2,
y2,
selDrawX + halfselw + (halfselw - textSelWidth) / 2,
kby + 260,
imeNames,
imeCodes.linearSearch { it == App.getConfigString("inputmethod") } ?: throw IME.LayoutNotFound(App.getConfigString("inputmethod")),
textSelWidth
@@ -138,7 +138,7 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
private val keyboardTestPanel = UIItemTextLineInput(this,
drawX + (width - 480) / 2 + 3,
height - 40,
drawY + height - 120,
474
)
@@ -171,23 +171,23 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
batch.color = Color.WHITE
val txt1 = Lang["MENU_LABEL_KEYBOARD_LAYOUT"]; val tw1 = App.fontGame.getWidth(txt1)
App.fontGame.draw(batch, txt1, selDrawX + (halfselw - tw1) / 2, y1)
App.fontGame.draw(batch, txt1, selDrawX + (halfselw - tw1) / 2, keyboardLayoutSelection.posY - 40)
val txt2 = Lang["MENU_LABEL_IME"]; val tw2 = App.fontGame.getWidth(txt2)
App.fontGame.draw(batch, txt2, selDrawX + halfselw + (halfselw - tw2) / 2, y1)
App.fontGame.draw(batch, txt2, selDrawX + halfselw + (halfselw - tw2) / 2, keyboardLayoutSelection.posY - 40)
// title
// todo show "Keyboard"/"Gamepad" accordingly
val title = Lang["MENU_CONTROLS_KEYBOARD"]
batch.color = Color.WHITE
App.fontGame.draw(batch, title, drawX.toFloat() + (width - App.fontGame.getWidth(title)) / 2, drawY.toFloat())
App.fontUITitle.draw(batch, title, drawX.toFloat() + (width - App.fontUITitle.getWidth(title)) / 2, drawY.toFloat())
// button help for string input UI
val help1 = "${Lang["MENU_LABEL_IME_TOGGLE"]}"
App.fontGame.draw(batch, help1, drawX + 10f, height - 40f - 28f)
App.fontGame.draw(batch, help1, drawX + 10f, keyboardTestPanel.posY - 28f)
val help2 = "${Lang["MENU_LABEL_PASTE_FROM_CLIPBOARD"]}"
App.fontGame.draw(batch, help2, drawX + keyboardTestPanel.width - 4f - App.fontGame.getWidth(help2), height - 40f + 30f)
App.fontGame.draw(batch, help2, drawX + keyboardTestPanel.width - 4f - App.fontGame.getWidth(help2), keyboardTestPanel.posY + 30f)

View File

@@ -50,7 +50,7 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
defaultSelection = null
)
private val areYouSureMainMenuButtons = UIItemTextButtonList(
this, DEFAULT_LINE_HEIGHT, arrayOf("MENU_LABEL_QUIT_CONFIRM", "MENU_LABEL_QUIT", "MENU_LABEL_CANCEL"),
this, DEFAULT_LINE_HEIGHT, arrayOf("MENU_LABEL_QUIT_CONFIRM", "MENU_LABEL_UNSAVED_PROGRESSES_WILL_BE_LOST", "MENU_LABEL_QUIT", "MENU_LABEL_CANCEL"),
(width - gameMenuListWidth) / 2,
INVENTORY_CELLS_OFFSET_Y() + (INVENTORY_CELLS_UI_HEIGHT - (DEFAULT_LINE_HEIGHT * 3)) / 2,
gameMenuListWidth, DEFAULT_LINE_HEIGHT * 3,
@@ -60,7 +60,12 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
highlightBackCol = Color(0),
inactiveCol = Color.WHITE,
defaultSelection = null
)
).also {
listOf(it.buttons[0], it.buttons[1]).forEach {
it.skipUpdate = true
it.isActive = false
}
}
/*private val areYouSureQuitButtons = UIItemTextButtonList(
this, DEFAULT_LINE_HEIGHT, arrayOf("MENU_LABEL_DESKTOP_QUESTION", "MENU_LABEL_DESKTOP", "MENU_LABEL_CANCEL"),
(width - gameMenuListWidth) / 2,
@@ -105,11 +110,19 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
full.unlockTransition()
}
val saveTime_t = App.getTIME_T()
val onSuccessful = {
// return to normal state
System.gc()
screen = 0
full.handler.unlockToggle()
full.unlockTransition()
(INGAME as TerrarumIngame).autosaveTimer = 0f
}
/*val saveTime_t = App.getTIME_T()
val playerSavefile = getPlayerSaveFiledesc(INGAME.playerSavefileName)
val worldSavefile = getWorldSaveFiledesc(INGAME.worldSavefileName)
INGAME.makeSavegameBackupCopy(playerSavefile)
WriteSavegame(saveTime_t, WriteSavegame.SaveMode.PLAYER, INGAME.playerDisk, playerSavefile, INGAME as TerrarumIngame, false, onError) {
@@ -123,13 +136,12 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
}
// return to normal state
System.gc()
screen = 0
full.handler.unlockToggle()
full.unlockTransition()
(INGAME as TerrarumIngame).autosaveTimer = 0f
onSuccessful()
}
}
}*/
INGAME.saveTheGame(onSuccessful, onError)
}
1 -> {
@@ -148,11 +160,11 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
}
areYouSureMainMenuButtons.selectionChangeListener = { _, new ->
when (new) {
1 -> {
2 -> {
areYouSureMainMenuButtons.deselect()
App.setScreen(TitleScreen(App.batch))
}
2 -> {
3 -> {
screen = 0; areYouSureMainMenuButtons.deselect()
}
}

View File

@@ -12,6 +12,7 @@ import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItemHorizontalFadeSlide
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import net.torvald.unicode.*
/**
@@ -31,8 +32,20 @@ class UIInventoryFull(
override var height: Int = App.scr.height
companion object {
private var shapeRenderer: ShapeRenderer? = null
// private var shapeRenderer: ShapeRenderer? = null
private val backDropsLoaded = Array<Boolean>(16) { false }
private val backdrop01: TextureRegionPack
get() {
if (!backDropsLoaded[0]) {
CommonResourcePool.addToLoadingList("basegame.uibackdrop01") {
TextureRegionPack(ModMgr.getGdxFile("basegame", "gui/backdrop01.tga"), 2, 140)
}
CommonResourcePool.loadAll()
backDropsLoaded[0] = true
}
return CommonResourcePool.getAsTextureRegionPack("basegame.uibackdrop01")
}
val CELL_COL = Toolkit.Theme.COL_CELL_FILL
@@ -58,6 +71,9 @@ class UIInventoryFull(
val INVENTORY_CELLS_OFFSET_X = { 0 + (Toolkit.drawWidth - internalWidth) / 2 }
val INVENTORY_CELLS_OFFSET_Y = { -YPOS_CORRECTION + 107 + (App.scr.height - internalHeight) / 2 }
fun getWidthOfCells(count: Int, cellWidth: Int = UIItemInventoryElemWide.height, gapWidth: Int = UIItemInventoryItemGrid.listGap) =
(cellWidth * count) + (gapWidth * (count - 1))
val catBarWidth = 330
val gradStartCol = Color(0x404040_60)
@@ -69,9 +85,11 @@ class UIInventoryFull(
private val gsta = Color(gradStartCol)
private val gend = Color(gradEndCol)
private val drawBackgroundColourBuffer = Color(1f,1f,1f,1f)
fun drawBackground(batch: SpriteBatch, opacity: Float) {
batch.end()
gdxBlendNormalStraightAlpha()
/*batch.end()
if (shapeRenderer == null) {
shapeRenderer = App.makeShapeRenderer()
@@ -101,7 +119,23 @@ class UIInventoryFull(
it.rect(0f, h, w, -(h - gradBottomEnd), gsta, gsta, gsta, gsta)
}
batch.begin()
batch.begin()*/
// drawBackgroundColourBuffer.a = opacity
batch.color = drawBackgroundColourBuffer
val w = App.scr.wf
val h = App.scr.hf
val gradTopStart = (-YPOS_CORRECTION + (App.scr.height - internalHeight).div(2).toFloat()) * App.scr.magn
val hTop = gradTopStart
val hTopRem = hTop - 64f
val hMid = h - 2 * (hTopRem + 140f)
batch.draw(backdrop01.get(0, 0), 0f, 0f, w, hTopRem)
batch.draw(backdrop01.get(0, 1), 0f, hTopRem, w, 140f)
batch.draw(backdrop01.get(0, 2), 0f, hTopRem + 140f, w, hMid)
batch.draw(backdrop01.get(0, 3), 0f, hTopRem + 140f + hMid, w, 140f)
batch.draw(backdrop01.get(0, 4), 0f, hTopRem + 280f + hMid, w, hTopRem)
}
}
@@ -281,7 +315,7 @@ class UIInventoryFull(
override fun renderUI(batch: SpriteBatch, camera: Camera) {
drawBackground(batch, handler.opacity)
drawBackground(batch, 1f)
// UI items
catBar.render(batch, camera)

View File

@@ -86,13 +86,18 @@ open class UIItemInventoryItemGrid(
private val inventoryUI = parentUI
var itemPage = 0
var itemPage
set(value) {
field = if (itemPageCount == 0) 0 else (value).fmod(itemPageCount)
navRemoCon.itemPage = if (itemPageCount == 0) 0 else (value).fmod(itemPageCount)
rebuild(currentFilter)
}
var itemPageCount = 1 // TODO total size of current category / items.size
protected set
get() = navRemoCon.itemPage
var itemPageCount // TODO total size of current category / items.size
protected set(value) {
navRemoCon.itemPageCount = value
}
get() = navRemoCon.itemPageCount
var inventorySortList = ArrayList<InventoryPair>()
protected var rebuildList = true
@@ -140,35 +145,37 @@ open class UIItemInventoryItemGrid(
fun createInvCellGenericTouchDownFun(listRebuildFun: () -> Unit): (GameItem?, Long, Int, Any?, UIItemInventoryCellBase) -> Unit {
return { item: GameItem?, amount: Long, button: Int, _, _ ->
if (item != null && Terrarum.ingame != null) {
// equip da shit
val itemEquipSlot = item.equipPosition
if (itemEquipSlot == GameItem.EquipPosition.NULL) {
TODO("Equip position is NULL, does this mean it's single-consume items like a potion? (from item: \"$item\" with itemID: ${item.originalID}/${item.dynamicID})")
}
val player = (Terrarum.ingame!! as TerrarumIngame).actorNowPlaying
if (player != null) {
if (item != ItemCodex[player.inventory.itemEquipped.get(itemEquipSlot)]) { // if this item is unequipped, equip it
player.equipItem(item)
// also equip on the quickslot
player.actorValue.getAsInt(AVKey.__PLAYER_QUICKSLOTSEL)?.let {
player.inventory.setQuickslotItem(it, item.dynamicID)
}
if (button == App.getConfigInt("config_mouseprimary")) {
if (item != null && Terrarum.ingame != null) {
// equip da shit
val itemEquipSlot = item.equipPosition
if (itemEquipSlot == GameItem.EquipPosition.NULL) {
TODO("Equip position is NULL, does this mean it's single-consume items like a potion? (from item: \"$item\" with itemID: ${item.originalID}/${item.dynamicID})")
}
else { // if not, unequip it
player.unequipItem(item)
val player = (Terrarum.ingame!! as TerrarumIngame).actorNowPlaying
if (player != null) {
// also unequip on the quickslot
player.actorValue.getAsInt(AVKey.__PLAYER_QUICKSLOTSEL)?.let {
player.inventory.setQuickslotItem(it, null)
if (item != ItemCodex[player.inventory.itemEquipped.get(itemEquipSlot)]) { // if this item is unequipped, equip it
player.equipItem(item)
// also equip on the quickslot
player.actorValue.getAsInt(AVKey.__PLAYER_QUICKSLOTSEL)?.let {
player.inventory.setQuickslotItem(it, item.dynamicID)
}
}
else { // if not, unequip it
player.unequipItem(item)
// also unequip on the quickslot
player.actorValue.getAsInt(AVKey.__PLAYER_QUICKSLOTSEL)?.let {
player.inventory.setQuickslotItem(it, null)
}
}
}
}
listRebuildFun()
}
listRebuildFun()
}
}
@@ -223,50 +230,9 @@ open class UIItemInventoryItemGrid(
}
private val iconPosX = if (drawScrollOnRightside)
posX + width + LIST_TO_CONTROL_GAP
posX + width
else
posX - LIST_TO_CONTROL_GAP - catBar.catIcons.tileW
/** Long/compact mode buttons */
val gridModeButtons = Array<UIItemImageButton>(2) { index ->
UIItemImageButton(
parentUI,
catBar.catIcons.get(index + 14, 0),
backgroundCol = Color(0),
activeBackCol = Color(0),
highlightBackCol = Color(0),
activeBackBlendMode = BlendMode.NORMAL,
activeCol = Toolkit.Theme.COL_MOUSE_UP,
initialX = iconPosX,
initialY = getIconPosY(index),
highlightable = true
)
}
private val scrollUpButton = UIItemImageButton(
parentUI,
catBar.catIcons.get(18, 0),
backgroundCol = Color(0),
activeBackCol = Color(0),
activeBackBlendMode = BlendMode.NORMAL,
activeCol = Toolkit.Theme.COL_MOUSE_UP,
initialX = iconPosX,
initialY = getIconPosY(2),
highlightable = false
)
private val scrollDownButton = UIItemImageButton(
parentUI,
catBar.catIcons.get(19, 0),
backgroundCol = Color(0),
activeBackCol = Color(0),
activeBackBlendMode = BlendMode.NORMAL,
activeCol = Toolkit.Theme.COL_MOUSE_UP,
initialX = iconPosX,
initialY = getIconPosY(3),
highlightable = false
)
posX - UIItemListNavBarVertical.LIST_TO_CONTROL_GAP - UIItemListNavBarVertical.WIDTH - 4
fun setCustomHighlightRuleMain(predicate: ((UIItemInventoryCellBase) -> Boolean)?) {
itemGrid.forEach { it.customHighlightRuleMain = predicate }
@@ -282,63 +248,53 @@ open class UIItemInventoryItemGrid(
itemPage = if (itemPageCount == 0) 0 else (itemPage + relativeAmount).fmod(itemPageCount)
}
val navRemoCon = UIItemListNavBarVertical(parentUI, iconPosX, posY + 8, height, true, if (isCompactMode) 1 else 0)
init {
// initially highlight grid mode buttons
if (!hideSidebar) {
gridModeButtons[if (isCompactMode) 1 else 0].highlighted = true
gridModeButtons[0].touchDownListener = { _, _, _, _ ->
navRemoCon.listButtonListener = { _, _ ->
isCompactMode = false
gridModeButtons[0].highlighted = true
gridModeButtons[1].highlighted = false
itemPage = 0
rebuild(currentFilter)
}
gridModeButtons[1].touchDownListener = { _, _, _, _ ->
navRemoCon.gridButtonListener = { _, _ ->
isCompactMode = true
gridModeButtons[0].highlighted = false
gridModeButtons[1].highlighted = true
itemPage = 0
rebuild(currentFilter)
}
scrollUpButton.clickOnceListener = { _, _, _ ->
scrollUpButton.highlighted = false
navRemoCon.scrollUpListener = { _, it ->
it.highlighted = false
scrollItemPage(-1)
}
scrollDownButton.clickOnceListener = { _, _, _ ->
scrollDownButton.highlighted = false
navRemoCon.scrollDownListener = { _, it ->
it.highlighted = false
scrollItemPage(1)
}
// if (is.mouseUp) handled by this.touchDown()
// draw wallet text
navRemoCon.extraDrawOpOnBottom = { ui, batch ->
if (drawWallet) {
batch.color = Color.WHITE
walletText.forEachIndexed { index, it ->
batch.draw(
walletFont.get(0, it - '0'),
ui.gridModeButtons[0].posX - 1f, // scroll button size: 20px, font width: 20 px
ui.gridModeButtons[0].posY + height - index * walletFont.tileH - 18f
)
}
}
}
}
}
private val upDownButtonGapToDots = 7 // apparent gap may vary depend on the texture itself
// private val upDownButtonGapToDots = 7 // apparent gap may vary depend on the texture itself
private fun getIconPosY(index: Int) =
posY + 8 + 26 * index
// private fun getIconPosY(index: Int) =
// posY + 8 + 26 * index
override fun render(batch: SpriteBatch, camera: Camera) {
val posXDelta = posX - oldPosX
itemGrid.forEach { it.posX += posXDelta }
itemList.forEach { it.posX += posXDelta }
if (!hideSidebar) {
gridModeButtons.forEach { it.posX += posXDelta }
scrollUpButton.posX += posXDelta
scrollDownButton.posX += posXDelta
}
fun getScrollDotYHeight(i: Int) = scrollUpButton.posY + 14 + upDownButtonGapToDots + 10 * i
scrollDownButton.posY = getScrollDotYHeight(itemPageCount) + upDownButtonGapToDots
// define each button's highlighted status from the list of forceHighlighted, then render the button
items.forEach {
if (useHighlightingManager) it.forceHighlighted = forceHighlightList.contains(it.item?.dynamicID)
@@ -346,41 +302,7 @@ open class UIItemInventoryItemGrid(
}
if (!hideSidebar) {
// draw the tray
batch.color = Toolkit.Theme.COL_CELL_FILL
Toolkit.fillArea(batch, iconPosX - 4, getIconPosY(0) - 8, 28, height)
// cell border
batch.color = colourTheme.cellHighlightNormalCol
Toolkit.drawBoxBorder(batch, iconPosX - 4, getIconPosY(0) - 8, 28, height)
gridModeButtons.forEach { it.render(batch, camera) }
scrollUpButton.render(batch, camera)
scrollDownButton.render(batch, camera)
// draw scroll dots
for (i in 0 until itemPageCount) {
val colour = if (i == itemPage) Color.WHITE else Color(0xffffff7f.toInt())
batch.color = colour
batch.draw(
catBar.catIcons.get(if (i == itemPage) 20 else 21, 0),
iconPosX.toFloat(),
getScrollDotYHeight(i).toFloat()
)
}
}
// draw wallet text
if (drawWallet) {
batch.color = Color.WHITE
walletText.forEachIndexed { index, it ->
batch.draw(
walletFont.get(0, it - '0'),
gridModeButtons[0].posX - 1f, // scroll button size: 20px, font width: 20 px
gridModeButtons[0].posY + height - index * walletFont.tileH - 18f
)
}
navRemoCon.render(batch, camera)
}
super.render(batch, camera)
@@ -424,9 +346,7 @@ open class UIItemInventoryItemGrid(
if (!hideSidebar) {
gridModeButtons.forEach { it.update(delta) }
scrollUpButton.update(delta)
scrollDownButton.update(delta)
navRemoCon.update(delta)
}
}
@@ -549,9 +469,7 @@ open class UIItemInventoryItemGrid(
items.forEach { if (it.mouseUp) it.touchDown(screenX, screenY, pointer, button) }
if (!hideSidebar) {
gridModeButtons.forEach { if (it.mouseUp) it.touchDown(screenX, screenY, pointer, button) }
if (scrollUpButton.mouseUp) scrollUpButton.touchDown(screenX, screenY, pointer, button)
if (scrollDownButton.mouseUp) scrollDownButton.touchDown(screenX, screenY, pointer, button)
navRemoCon.touchDown(screenX, screenY, pointer, button)
}
return true
}

View File

@@ -0,0 +1,178 @@
package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.BlendMode
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.toInt
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItem
import net.torvald.terrarum.ui.UIItemImageButton
/**
* Created by minjaesong on 2023-06-17.
*/
class UIItemListNavBarVertical(
parentUI: UICanvas, initialX: Int, initialY: Int,
override val height: Int,
val hasGridModeButtons: Boolean, initialModeSelection: Int = 0,
private val colourTheme: InventoryCellColourTheme = UIItemInventoryCellCommonRes.defaultInventoryCellTheme,
var extraDrawOpOnBottom: (UIItemListNavBarVertical, SpriteBatch) -> Unit = { _,_ -> }
) : UIItem(parentUI, initialX, initialY) {
override val width = UIItemListNavBarVertical.WIDTH
companion object {
const val WIDTH = 28
const val LIST_TO_CONTROL_GAP = 12
}
private fun getIconPosY(index: Int) =
posY + 26 * index
private val catIcons = CommonResourcePool.getAsTextureRegionPack("inventory_category")
private val iconPosX = posX + LIST_TO_CONTROL_GAP
val gridModeButtons = Array<UIItemImageButton>(2) { index ->
UIItemImageButton(
parentUI,
catIcons.get(index + 14, 0),
backgroundCol = Color(0),
activeBackCol = Color(0),
highlightBackCol = Color(0),
activeBackBlendMode = BlendMode.NORMAL,
activeCol = Toolkit.Theme.COL_MOUSE_UP,
initialX = iconPosX,
initialY = getIconPosY(index),
highlightable = true
)
}
val scrollUpButton = UIItemImageButton(
parentUI,
catIcons.get(18, 0),
backgroundCol = Color(0),
activeBackCol = Color(0),
activeBackBlendMode = BlendMode.NORMAL,
activeCol = Toolkit.Theme.COL_MOUSE_UP,
initialX = iconPosX,
initialY = getIconPosY(2 - (!hasGridModeButtons).toInt(1)),
highlightable = false
)
val scrollDownButton = UIItemImageButton(
parentUI,
catIcons.get(19, 0),
backgroundCol = Color(0),
activeBackCol = Color(0),
activeBackBlendMode = BlendMode.NORMAL,
activeCol = Toolkit.Theme.COL_MOUSE_UP,
initialX = iconPosX,
initialY = getIconPosY(3 - (!hasGridModeButtons).toInt(1)),
highlightable = false
)
private val upDownButtonGapToDots = 7 // apparent gap may vary depend on the texture itself
init {
gridModeButtons[initialModeSelection].highlighted = true
gridModeButtons[0].touchDownListener = { _, _, _, _ ->
gridModeButtons[0].highlighted = true
gridModeButtons[1].highlighted = false
itemPage = 0
listButtonListener(this, gridModeButtons[0])
}
gridModeButtons[1].touchDownListener = { _, _, _, _ ->
gridModeButtons[0].highlighted = false
gridModeButtons[1].highlighted = true
itemPage = 0
gridButtonListener(this, gridModeButtons[1])
}
scrollUpButton.clickOnceListener = { _, _ ->
scrollUpButton.highlighted = false
scrollUpListener(this, scrollUpButton)
}
scrollDownButton.clickOnceListener = { _, _ ->
scrollDownButton.highlighted = false
scrollDownListener(this, scrollDownButton)
}
}
var listButtonListener: (UIItemListNavBarVertical, UIItemImageButton) -> Unit = { _,_ -> }
var gridButtonListener: (UIItemListNavBarVertical, UIItemImageButton) -> Unit = { _,_ -> }
var scrollUpListener: (UIItemListNavBarVertical, UIItemImageButton) -> Unit = { _,_ -> }
var scrollDownListener: (UIItemListNavBarVertical, UIItemImageButton) -> Unit = { _,_ -> }
var itemPageCount = 0
var itemPage = 0
override fun render(batch: SpriteBatch, camera: Camera) {
val posXDelta = posX - oldPosX
gridModeButtons.forEach { it.posX += posXDelta }
scrollUpButton.posX += posXDelta
scrollDownButton.posX += posXDelta
fun getScrollDotYHeight(i: Int) = scrollUpButton.posY + 14 + upDownButtonGapToDots + 10 * i
scrollDownButton.posY = getScrollDotYHeight(itemPageCount) + upDownButtonGapToDots
// draw the tray
batch.color = Toolkit.Theme.COL_CELL_FILL
Toolkit.fillArea(batch, iconPosX - 4, getIconPosY(0) - 8, width, height)
// cell border
batch.color = colourTheme.cellHighlightNormalCol
Toolkit.drawBoxBorder(batch, iconPosX - 4, getIconPosY(0) - 8, width, height)
if (hasGridModeButtons) gridModeButtons.forEach { it.render(batch, camera) }
scrollUpButton.render(batch, camera)
scrollDownButton.render(batch, camera)
// draw scroll dots
for (i in 0 until itemPageCount) {
val colour = if (i == itemPage) Color.WHITE else Color(0xffffff7f.toInt())
batch.color = colour
batch.draw(
catIcons.get(if (i == itemPage) 20 else 21, 0),
iconPosX.toFloat(),
getScrollDotYHeight(i) - 2f
)
}
extraDrawOpOnBottom(this, batch)
super.render(batch, camera)
oldPosX = posX
}
override fun update(delta: Float) {
if (hasGridModeButtons) gridModeButtons.forEach { it.update(delta) }
scrollUpButton.update(delta)
scrollDownButton.update(delta)
super.update(delta)
}
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
if (hasGridModeButtons) gridModeButtons.forEach { if (it.mouseUp) it.touchDown(screenX, screenY, pointer, button) }
if (scrollUpButton.mouseUp) scrollUpButton.touchDown(screenX, screenY, pointer, button)
if (scrollDownButton.mouseUp) scrollDownButton.touchDown(screenX, screenY, pointer, button)
return super.touchDown(screenX, screenY, pointer, button)
}
override fun dispose() {
}
}

View File

@@ -6,6 +6,7 @@ import net.torvald.terrarum.App
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.serialise.WriteSavegame
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItem
import kotlin.math.roundToInt
@@ -30,9 +31,10 @@ class UIItemSaving(parentUI: UICanvas, initialX: Int, initialY: Int) : UIItem(pa
}
override fun render(batch: SpriteBatch, camera: Camera) {
// these things will not scroll along with the parent GUI!
val t = Lang["MENU_IO_SAVING"]
val tlen = App.fontGame.getWidth(t)
App.fontGame.draw(batch, t, (posX + (width - tlen) / 2).toFloat(), posY - 32f)
App.fontGame.draw(batch, t, (posX + (width - tlen) / 2).toFloat(), ((App.scr.height - circleSheet.tileH) / 2) - 40f)
// -1..63
val index = ((WriteSavegame.saveProgress / WriteSavegame.saveProgressMax) * circles).roundToInt() - 1
@@ -41,7 +43,11 @@ class UIItemSaving(parentUI: UICanvas, initialX: Int, initialY: Int) : UIItem(pa
val sy = index / circleSheet.horizontalCount
// q&d fix for ArrayIndexOutOfBoundsException caused when saving huge world... wut?
if (sx in 0 until circleSheet.horizontalCount && sy in 0 until circleSheet.horizontalCount) {
batch.draw(circleSheet.get(sx, sy), (posX + (width - circleSheet.tileW) / 2).toFloat(), posY.toFloat())
batch.draw(
circleSheet.get(sx, sy),
((Toolkit.drawWidth - circleSheet.tileW) / 2).toFloat(),
((App.scr.height - circleSheet.tileH) / 2).toFloat()
)
}
}
}

View File

@@ -122,7 +122,7 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() {
keycaps.values.forEach { addUIitem(it) }
updateKeycaps()
buttonReset.clickOnceListener = { x, y, button ->
buttonReset.clickOnceListener = { x, y ->
resetKeyConfig()
updateKeycaps()
}
@@ -201,7 +201,9 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() {
// todo show "Keyboard"/"Gamepad" accordingly
batch.color = Color.WHITE
val title = Lang["MENU_CONTROLS_KEYBOARD"]
App.fontGame.draw(batch, title, drawX.toFloat() + (width - App.fontGame.getWidth(title)) / 2, drawY.toFloat())
App.fontUITitle.draw(batch, title, drawX.toFloat() + (width - App.fontUITitle.getWidth(title)) / 2, drawY.toFloat())
val desc = Lang["MENU_LABEL_KEYCONFIG_HELP1"]
App.fontGame.draw(batch, desc, drawX.toFloat() + (width - App.fontGame.getWidth(desc)) / 2, drawY + 360f)

View File

@@ -36,6 +36,8 @@ import net.torvald.terrarum.ui.Movement
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItem
import net.torvald.terrarum.utils.JsonFetcher
import net.torvald.terrarum.utils.forEachSiblings
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import java.time.Instant
import java.time.format.DateTimeFormatter
@@ -55,6 +57,11 @@ val SAVE_CELL_HEIGHT = 120
* WARNING: the values are not guaranteed to reset when the selector UI is closed!
*/
object UILoadGovernor {
// used by the default save loader
var playerUUID: UUID? = null
var worldUUID: UUID? = null
var previousSaveWasLoaded = false
// used by the debug save loader
var playerDisk: DiskSkimmer? = null
set(value) {
printdbg(this, "Player selected: ${value?.diskFile?.name}")
@@ -71,15 +78,23 @@ object UILoadGovernor {
printdbg(this, "Resetting player and world selection")
playerDisk = null
worldDisk = null
playerUUID = null
worldUUID = null
previousSaveWasLoaded = false
}
}
abstract class Advanceable : UICanvas() {
abstract fun advanceMode(button: UIItem)
}
/**
* Only works if current screen set by the App is [TitleScreen]
*
* Created by minjaesong on 2021-09-09.
*/
class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
class UILoadDemoSavefiles(val remoCon: UIRemoCon) : Advanceable() {
// private val hash = RandomWordsName(3)
@@ -155,7 +170,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
this.mode = mode
}
fun advanceMode() {
override fun advanceMode(button: UIItem) {
mode += 1
uiScroll = 0f
scrollFrom = 0
@@ -175,7 +190,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
// read savegames
var savegamesCount = 0
App.sortedSavegameWorlds.forEach { uuid ->
val skimmer = App.savegameWorlds[uuid]!!
val skimmer = App.savegameWorlds[uuid]!!.loadable()
val x = uiX
val y = titleTopGradEnd + cellInterval * savegamesCount
try {
@@ -190,7 +205,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
savegamesCount = 0
App.sortedPlayers.forEach { uuid ->
val skimmer = App.savegamePlayers[uuid]!!
val skimmer = App.savegamePlayers[uuid]!!.loadable()
val x = uiX
val y = titleTopGradEnd + cellInterval * savegamesCount
try {
@@ -374,7 +389,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
// draw texts
val loadGameTitleStr = Lang[titles[mode]]// + "$EMDASH$hash"
// "Game Load"
App.fontUITitle.draw(batch, loadGameTitleStr, (width - App.fontGame.getWidth(loadGameTitleStr)).div(2).toFloat(), titleTextPosY.toFloat())
App.fontUITitle.draw(batch, loadGameTitleStr, (width - App.fontUITitle.getWidth(loadGameTitleStr)).div(2).toFloat(), titleTextPosY.toFloat())
// Control help
App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat())
}
@@ -470,7 +485,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
class UIItemPlayerCells(
parent: UILoadDemoSavefiles,
parent: Advanceable,
initialX: Int,
initialY: Int,
val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) {
@@ -478,9 +493,11 @@ class UIItemPlayerCells(
override val width = SAVE_CELL_WIDTH
override val height = SAVE_CELL_HEIGHT
override var clickOnceListener: ((Int, Int, Int) -> Unit)? = { _: Int, _: Int, _: Int ->
override var clickOnceListener: ((Int, Int) -> Unit) = { _: Int, _: Int ->
UILoadGovernor.playerDisk = skimmer
parent.advanceMode()
UILoadGovernor.playerUUID = playerUUID
UILoadGovernor.worldUUID = worldUUID
parent.advanceMode(this)
}
private var playerName: String = "$EMDASH"
@@ -488,28 +505,25 @@ class UIItemPlayerCells(
private var lastPlayTime: String = "????-??-?? --:--:--"
private var totalPlayTime: String = "--h--m--s"
private var playerUUID: UUID? = null
lateinit var playerUUID: UUID; private set
lateinit var worldUUID: UUID; private set
init {
skimmer.getFile(SAVEGAMEINFO)?.bytes?.let {
val json = JsonReader().parse(ByteArray64Reader(it, Common.CHARSET))
var lastPlayTime0 = 0L
playerUUID = UUID.fromString(json["uuid"]?.asString())
val worldUUID = UUID.fromString(json["worldCurrentlyPlaying"]?.asString())
JsonFetcher.readFromJsonString(ByteArray64Reader(it, Common.CHARSET)).forEachSiblings { name, value ->
if (name == "uuid") playerUUID = UUID.fromString(value.asString())
if (name == "worldCurrentlyPlaying") worldUUID = UUID.fromString(value.asString())
if (name == "totalPlayTime") totalPlayTime = parseDuration(value.asLong())
if (name == "lastPlayTime") lastPlayTime0 = value.asLong()
}
App.savegamePlayersName[playerUUID]?.let { if (it.isNotBlank()) playerName = it else "(name)" }
App.savegameWorldsName[worldUUID]?.let { if (it.isNotBlank()) worldName = it }
/*json["lastPlayTime"]?.asString()?.let {
lastPlayTime = Instant.ofEpochSecond(it.toLong())
.atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}*/
lastPlayTime = Instant.ofEpochSecond(skimmer.getLastModifiedTime())
lastPlayTime = Instant.ofEpochSecond(lastPlayTime0)
.atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
json["totalPlayTime"]?.asString()?.let {
totalPlayTime = parseDuration(it.toLong())
}
}
}
@@ -541,10 +555,12 @@ class UIItemPlayerCells(
private var highlightCol: Color = defaultCol
private var highlightTextCol: Color = defaultCol
var forceMouseDown = false
override fun update(delta: Float) {
super.update(delta)
highlightCol = if (mouseUp) litCol else defaultCol
highlightTextCol = if (mouseUp) litCol else Toolkit.Theme.COL_LIST_DEFAULT
highlightCol = if (mouseUp && !forceMouseDown) litCol else defaultCol
highlightTextCol = if (mouseUp && !forceMouseDown) litCol else Toolkit.Theme.COL_LIST_DEFAULT
}
override fun render(batch: SpriteBatch, camera: Camera) {
@@ -670,7 +686,7 @@ class UIItemPlayerCells(
class UIItemWorldCells(
parent: UILoadDemoSavefiles,
parent: Advanceable,
initialX: Int,
initialY: Int,
val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) {
@@ -685,9 +701,6 @@ class UIItemWorldCells(
private val lastPlayedTimestamp: String
init {
printdbg(this, "Rebuilding skimmer for savefile ${skimmer.diskFile.absolutePath}")
skimmer.rebuild()
metaFile = skimmer.getFile(-1)
if (metaFile == null) saveDamaged = true
@@ -699,14 +712,18 @@ class UIItemWorldCells(
saveDamaged = saveDamaged or checkForSavegameDamage(skimmer)
if (metaFile != null) {
val worldJson = JsonReader().parse(ByteArray64Reader(metaFile.bytes, Common.CHARSET))
val lastplay_t = skimmer.getLastModifiedTime()//worldJson["lastPlayTime"].asLong()
val playtime_t = worldJson["totalPlayTime"].asLong()
// val lastplay_t = skimmer.getLastModifiedTime()//worldJson["lastPlayTime"].asLong()
var playtime_t = ""
var lastplay_t = 0L
JsonFetcher.readFromJsonString(ByteArray64Reader(metaFile.bytes, Common.CHARSET)).forEachSiblings { name, value ->
if (name == "lastPlayTime") lastplay_t = value.asLong()
if (name == "totalPlayTime") playtime_t = parseDuration(value.asLong())
}
lastPlayedTimestamp =
Instant.ofEpochSecond(lastplay_t)
.atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +
"/${parseDuration(playtime_t)}"
"/$playtime_t"
}
else {
lastPlayedTimestamp = "--:--:--/--h--m--s"
@@ -739,9 +756,9 @@ class UIItemWorldCells(
private var highlightCol: Color = Toolkit.Theme.COL_LIST_DEFAULT
override var clickOnceListener: ((Int, Int, Int) -> Unit)? = { _: Int, _: Int, _: Int ->
override var clickOnceListener: ((Int, Int) -> Unit) = { _: Int, _: Int ->
UILoadGovernor.worldDisk = skimmer
parent.advanceMode()
parent.advanceMode(this)
}
internal var hasTexture = false

View File

@@ -0,0 +1,709 @@
package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.graphics.glutils.FrameBuffer
import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.unicode.getKeycapConsole
import net.torvald.unicode.getKeycapPC
import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.savegame.ByteArray64InputStream
import net.torvald.terrarum.savegame.EntryFile
import net.torvald.terrarum.modulebasegame.serialise.LoadSavegame
import net.torvald.terrarum.savegame.VDFileID.PLAYER_SCREENSHOT
import net.torvald.terrarum.ui.*
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.zip.GZIPInputStream
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
/**
* Only works if current screen set by the App is [TitleScreen]
*
* Created by minjaesong on 2023-06-24.
*/
class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
// private val hash = RandomWordsName(3)
init {
CommonResourcePool.addToLoadingList("terrarum-defaultsavegamethumb") {
TextureRegion(Texture(Gdx.files.internal("assets/graphics/gui/savegame_thumb_placeholder.png")))
}
CommonResourcePool.addToLoadingList("savegame_status_icon") {
TextureRegionPack("assets/graphics/gui/savegame_status_icon.tga", 24, 24)
}
CommonResourcePool.loadAll()
}
override var width: Int
get() = Toolkit.drawWidth
set(value) {}
override var height: Int
get() = App.scr.height
set(value) {}
override var openCloseTime: Second = OPENCLOSE_GENERIC
private val shapeRenderer = App.makeShapeRenderer()
internal val uiWidth = SAVE_CELL_WIDTH
internal val uiX: Int
get() = (Toolkit.drawWidth - uiWidth) / 2
internal val uiXdiffChatOverlay = App.scr.chatWidth / 2
internal val textH = App.fontGame.lineHeight.toInt()
internal val cellGap = 20
internal val cellInterval = cellGap + SAVE_CELL_HEIGHT
internal val gradAreaHeight = 32
// internal val titleTextPosY: Int = App.scr.tvSafeGraphicsHeight + 10
internal val titleTopGradStart: Int = App.scr.tvSafeGraphicsHeight
internal val titleTopGradEnd: Int = titleTopGradStart + gradAreaHeight
internal val titleBottomGradStart: Int = height - App.scr.tvSafeGraphicsHeight - gradAreaHeight
internal val titleBottomGradEnd: Int = titleBottomGradStart + gradAreaHeight
internal val controlHelperY: Int = titleBottomGradStart + gradAreaHeight - textH
private val controlHelp: String
get() = if (App.environment == RunningEnvironment.PC)
"${getKeycapPC(App.getConfigInt("control_key_up"))}${getKeycapPC(App.getConfigInt("control_key_down"))}" +
" ${Lang["MENU_CONTROLS_SCROLL"]}"
else
"${getKeycapConsole('R')} ${Lang["MENU_CONTROLS_SCROLL"]}"
private var scrollAreaHeight = height - 2 * App.scr.tvSafeGraphicsHeight - 64
private var listScroll = 0 // only update when animation is finished
private var savesVisible = (scrollAreaHeight + cellGap) / cellInterval
private var uiScroll = 0f
private var scrollFrom = 0
private var scrollTarget = 0
private var scrollAnimCounter = 0f
private val scrollAnimLen = 0.1f
private var sliderFBO = FrameBuffer(Pixmap.Format.RGBA8888, uiWidth + 10, height, false)
private var showSpinner = false
private val playerCells = ArrayList<UIItemPlayerCells>()
var mode = 0 // 0: show players, 1: show worlds
private set(value) {
touchLatched = true
field = value
}
private val MODE_SELECT = 0
private val MODE_SELECT_AFTER = 1
private val MODE_SAVE_MULTIPLE_CHOICES = 2
private val MODE_LOAD_DA_SHIT_ALREADY = 255
private val MODE_SAVE_DAMAGED = 256
private val MODE_SAVE_DELETE = 512
private val MODE_SAVE_DELETE_CONFIRM = 513
private var buttonSelectedForDeletion: UIItemPlayerCells? = null
private val goButtonWidth = 180
private val drawX = (Toolkit.drawWidth - 480) / 2
private val drawY = (App.scr.height - 480) / 2
private val corruptedBackButton = UIItemTextButton(this, "MENU_LABEL_BACK", (Toolkit.drawWidth - goButtonWidth) / 2, drawY + 480 - 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
private val confirmCancelButton = UIItemTextButton(this, "MENU_LABEL_CANCEL", drawX + (240 - goButtonWidth) / 2, drawY + 480 - 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
private val confirmDeleteButton = UIItemTextButton(this, "MENU_LABEL_DELETE", drawX + 240 + (240 - goButtonWidth) / 2, drawY + 480- 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true, inactiveCol = Toolkit.Theme.COL_RED, activeCol = Toolkit.Theme.COL_REDD)
private lateinit var loadables: SavegameCollectionPair
private lateinit var loadManualThumbButton: UIItemImageButton
private lateinit var loadAutoThumbButton: UIItemImageButton
private val disposablePool = ArrayList<Disposable>()
private fun DiskPair.getThumbnail(): TextureRegion {
return this.player.requestFile(PLAYER_SCREENSHOT).let { file ->
CommonResourcePool.getAsTextureRegion("terrarum-defaultsavegamethumb")
if (file != null) {
val zippedTga = (file.contents as EntryFile).bytes
val gzin = GZIPInputStream(ByteArray64InputStream(zippedTga))
val tgaFileContents = gzin.readAllBytes(); gzin.close()
val pixmap = Pixmap(tgaFileContents, 0, tgaFileContents.size)
TextureRegion(Texture(pixmap)).also {
disposablePool.add(it.texture)
// do cropping and resizing
it.setRegion(
(pixmap.width - imageButtonW*2) / 2,
(pixmap.height - imageButtonH*2) / 2,
imageButtonW * 2,
imageButtonH * 2
)
}
}
else {
CommonResourcePool.getAsTextureRegion("terrarum-defaultsavegamethumb")
}
}
}
private val altSelDrawW = 640
private val altSelHdrawW = altSelDrawW / 2
private val altSelDrawH = 480
private val imageButtonW = 300
private val imageButtonH = 240
private val altSelDrawY = ((App.scr.height - altSelDrawH)/2)
private val altSelQdrawW = altSelDrawW / 4
private val altSelQQQdrawW = altSelDrawW * 3 / 4
private fun getDrawTextualInfoFun(disks: DiskPair): (UIItem, SpriteBatch) -> Unit {
val lastPlayedStamp = Instant.ofEpochSecond(disks.player.getLastModifiedTime())
.atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
return { item: UIItem, batch: SpriteBatch ->
App.fontSmallNumbers.draw(batch, lastPlayedStamp, item.posX + 5f, item.posY + 3f)
}
}
init {
corruptedBackButton.clickOnceListener = { _,_ -> remoCon.openUI(UILoadSavegame(remoCon)) }
confirmCancelButton.clickOnceListener = { _, _ -> remoCon.openUI(UILoadSavegame(remoCon)) }
confirmDeleteButton.clickOnceListener = { _,_ ->
val pu = buttonSelectedForDeletion!!.playerUUID
val wu = buttonSelectedForDeletion!!.worldUUID
App.savegamePlayers[pu]?.moveToRecycle(App.recycledPlayersDir)?.let {
App.sortedPlayers.remove(pu)
App.savegamePlayers.remove(pu)
App.savegamePlayersName.remove(pu)
}
// don't delete the world please
remoCon.openUI(UILoadSavegame(remoCon))
}
}
override fun advanceMode(button: UIItem) {
printdbg(this, "advanceMode ${button.javaClass.canonicalName}")
mode += 1
uiScroll = 0f
scrollFrom = 0
scrollTarget = 0
scrollAnimCounter = 0f
loadFired = 0
printdbg(this, "savelist mode: $mode")
// look for recently played world
if (mode == MODE_SELECT_AFTER) {
// select the most recent loadable save by comparing manual and autosaves, NOT JUST going with loadable()
printdbg(this, "Load playerUUID: ${UILoadGovernor.playerUUID}, worldUUID: ${UILoadGovernor.worldUUID}")
loadables = SavegameCollectionPair(App.savegamePlayers[UILoadGovernor.playerUUID], App.savegameWorlds[UILoadGovernor.worldUUID])
mode = if (loadables.moreRecentAutosaveAvailable()) {
// make choice for load manual or auto, if available
val autoThumb = loadables.getAutoSave()!!.getThumbnail()
val manualThumb = loadables.getManualSave()!!.getThumbnail()
loadManualThumbButton = UIItemImageButton(this, manualThumb,
initialX = (Toolkit.drawWidth - altSelDrawW)/2 + altSelQdrawW - imageButtonW/2,
initialY = altSelDrawY + 120,
width = imageButtonW,
height = imageButtonH,
imageDrawWidth = imageButtonW,
imageDrawHeight = imageButtonH,
highlightable = false,
useBorder = true,
).also {
it.extraDrawOp = getDrawTextualInfoFun(loadables.getManualSave()!!)
it.clickOnceListener = { _,_ ->
loadables.getManualSave()!!.let {
UILoadGovernor.playerDisk = it.player
UILoadGovernor.worldDisk = it.world
}
mode = MODE_LOAD_DA_SHIT_ALREADY
}
}
loadAutoThumbButton = UIItemImageButton(this, autoThumb,
initialX = (Toolkit.drawWidth - altSelDrawW)/2 + altSelQQQdrawW - imageButtonW/2,
initialY = altSelDrawY + 120,
width = imageButtonW,
height = imageButtonH,
imageDrawWidth = imageButtonW,
imageDrawHeight = imageButtonH,
highlightable = false,
useBorder = true,
).also {
it.extraDrawOp = getDrawTextualInfoFun(loadables.getAutoSave()!!)
it.clickOnceListener = { _,_ ->
loadables.getAutoSave()!!.let {
UILoadGovernor.playerDisk = it.player
UILoadGovernor.worldDisk = it.world
}
mode = MODE_LOAD_DA_SHIT_ALREADY
}
}
MODE_SAVE_MULTIPLE_CHOICES
}
else if (!loadables.saveAvaliable()) {
// show save is damaged and cannot be loaded
MODE_SAVE_DAMAGED
}
else {
val (p, w) = loadables.getLoadableSave()!!
UILoadGovernor.playerDisk = p; UILoadGovernor.worldDisk = w
if (loadables.newerSaveIsDamaged) {
UILoadGovernor.previousSaveWasLoaded = true
}
MODE_LOAD_DA_SHIT_ALREADY
// test codes //
/*val autoThumb = loadables.getManualSave()!!.getThumbnail()
val manualThumb = loadables.getManualSave()!!.getThumbnail()
loadManualThumbButton = UIItemImageButton(this, manualThumb,
initialX = (Toolkit.drawWidth - altSelDrawW)/2 + altSelQdrawW - imageButtonW/2,
initialY = altSelDrawY + 120,
width = imageButtonW,
height = imageButtonH,
imageDrawWidth = imageButtonW,
imageDrawHeight = imageButtonH,
highlightable = false,
useBorder = true,
).also {
it.extraDrawOp = getDrawTextualInfoFun(loadables.getManualSave()!!)
it.clickOnceListener = { _,_ ->
loadables.getManualSave()!!.let {
UILoadGovernor.playerDisk = it.player
UILoadGovernor.worldDisk = it.world
}
mode = MODE_LOAD_DA_SHIT_ALREADY
}
}
loadAutoThumbButton = UIItemImageButton(this, autoThumb,
initialX = (Toolkit.drawWidth - altSelDrawW)/2 + altSelQQQdrawW - imageButtonW/2,
initialY = altSelDrawY + 120,
width = imageButtonW,
height = imageButtonH,
imageDrawWidth = imageButtonW,
imageDrawHeight = imageButtonH,
highlightable = false,
useBorder = true,
).also {
it.extraDrawOp = getDrawTextualInfoFun(loadables.getManualSave()!!)
it.clickOnceListener = { _,_ ->
loadables.getManualSave()!!.let {
UILoadGovernor.playerDisk = it.player
UILoadGovernor.worldDisk = it.world
}
mode = MODE_LOAD_DA_SHIT_ALREADY
}
}
MODE_SAVE_MULTIPLE_CHOICES*/
}
printdbg(this, "mode = $mode")
}
else if (mode == MODE_SAVE_DELETE_CONFIRM) {
// confirm deletion of selected player
buttonSelectedForDeletion = (button as UIItemPlayerCells).also {
deleteCellPosYstart = it.posY.toFloat()
it.forceMouseDown = true
it.update(0.01f)
}
}
}
override fun show() {
try {
remoCon.handler.lockToggle()
showSpinner = true
Thread {
// read savegames
var savegamesCount = 0
App.sortedPlayers.forEach { uuid ->
val skimmer = App.savegamePlayers[uuid]!!.loadable()
val x = uiX
val y = titleTopGradEnd + cellInterval * savegamesCount
try {
playerCells.add(UIItemPlayerCells(this, x, y, skimmer))
savegamesCount += 1
}
catch (e: Throwable) {
System.err.println("[UILoadSavegame] Error while loading Player '${skimmer.diskFile.absolutePath}'")
e.printStackTrace()
}
}
remoCon.handler.unlockToggle()
showSpinner = false
}.start()
}
catch (e: UninitializedPropertyAccessException) {}
}
override fun hide() {
playerCells.forEach { it.dispose() }
playerCells.clear()
}
private var touchLatched = false
private fun getCells() = playerCells
private var loadFired = 0
private var oldMode = -1
private val mode1Node = Yaml(UITitleRemoConYaml.injectedMenuSingleCharSel).parse()
// private val mode2Node = Yaml(UITitleRemoConYaml.injectedMenuSingleWorldSel).parse()
// private val menus = listOf(mode1Node, mode2Node)
private val deleteCharacterButton = UIItemTextButton(
this, "CONTEXT_CHARACTER_DELETE",
UIRemoCon.menubarOffX - UIRemoCon.UIRemoConElement.paddingLeft + 72,
UIRemoCon.menubarOffY - UIRemoCon.UIRemoConElement.lineHeight * 3 + 16,
remoCon.width + UIRemoCon.UIRemoConElement.paddingLeft,
true,
inactiveCol = Toolkit.Theme.COL_RED,
activeCol = Toolkit.Theme.COL_REDD,
hitboxSize = UIRemoCon.UIRemoConElement.lineHeight - 2,
alignment = UIItemTextButton.Companion.Alignment.LEFT
).also {
it.clickOnceListener = { _,_ ->
mode = MODE_SAVE_DELETE
it.highlighted = true
}
}
init {
// this UI will NOT persist; the parent of the mode1Node must be set using an absolute value (e.g. treeRoot, not remoCon.currentRemoConContents)
//printdbg(this, "UILoadSavegame called, from:")
//printStackTrace(this)
mode1Node.parent = remoCon.treeRoot
// mode2Node.parent = mode1Node
mode1Node.data = "MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame"
// mode2Node.data = "MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame"
// printdbg(this, "mode1Node parent: ${mode1Node.parent?.data}") // will be 'null' because the parent is the root node
// printdbg(this, "mode1Node data: ${mode1Node.data}")
// printdbg(this, "mode2Node data: ${mode2Node.data}")
}
private fun modeChangedHandler(mode: Int) {
printdbg(this, "Change mode: $oldMode -> $mode")
// remoCon.setNewRemoConContents(menus[mode])
remoCon.setNewRemoConContents(mode1Node)
}
override fun updateUI(delta: Float) {
if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
if (oldMode != mode) {
modeChangedHandler(mode)
oldMode = mode
}
if (scrollTarget != listScroll) {
if (scrollAnimCounter < scrollAnimLen) {
scrollAnimCounter += delta
uiScroll = Movement.fastPullOut(
scrollAnimCounter / scrollAnimLen,
listScroll * cellInterval.toFloat(),
scrollTarget * cellInterval.toFloat()
)
}
else {
scrollAnimCounter = 0f
listScroll = scrollTarget
uiScroll = cellInterval.toFloat() * scrollTarget
}
}
val cells = getCells()
for (index in 0 until cells.size) {
val it = cells[index]
if (index in listScroll - 2 until listScroll + savesVisible + 2) {
// re-position
it.posY = (it.initialY - uiScroll).roundToInt()
it.update(delta)
}
}
}
if (mode == MODE_SAVE_DELETE_CONFIRM && deleteCellAnimCounter <= scrollAnimLen) {
// do transitional moving stuff
buttonSelectedForDeletion?.posY = Movement.fastPullOut(deleteCellAnimCounter / scrollAnimLen, deleteCellPosYstart, (titleTopGradEnd + cellInterval).toFloat()).roundToInt()
deleteCellAnimCounter += delta
if (deleteCellAnimCounter > scrollAnimLen) deleteCellAnimCounter = scrollAnimLen
}
}
private var deleteCellAnimCounter = 0f
private var deleteCellPosYstart = 0f
override fun renderUI(batch: SpriteBatch, camera: Camera) {
if (mode == MODE_LOAD_DA_SHIT_ALREADY) {
loadFired += 1
// to hide the "flipped skybox" artefact
batch.end()
gdxClearAndEnableBlend(.094f, .094f, .094f, 0f)
batch.begin()
batch.color = Color.WHITE
val txt = Lang["MENU_IO_LOADING"]
App.fontGame.draw(batch, txt, (App.scr.width - App.fontGame.getWidth(txt)) / 2f, (App.scr.height - App.fontGame.lineHeight) / 2f)
if (loadFired == 2) {
LoadSavegame(UILoadGovernor.playerDisk!!, UILoadGovernor.worldDisk)
}
}
else if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
batch.end()
val cells = getCells()
lateinit var savePixmap: Pixmap
sliderFBO.inAction(camera as OrthographicCamera, batch) {
gdxClearAndEnableBlend(0f, 0f, 0f, 0f)
setCameraPosition(batch, camera, 0f, 0f)
batch.color = Color.WHITE
batch.inUse {
for (index in 0 until cells.size) {
val it = cells[index]
if (App.getConfigBoolean("fx_streamerslayout"))
it.posX += uiXdiffChatOverlay
if (index in listScroll - 2 until listScroll + savesVisible + 2)
it.render(batch, camera)
if (App.getConfigBoolean("fx_streamerslayout"))
it.posX -= uiXdiffChatOverlay
}
}
savePixmap = Pixmap.createFromFrameBuffer(0, 0, sliderFBO.width, sliderFBO.height)
savePixmap.blending = Pixmap.Blending.None
}
// implement "wipe-out" by CPU-rendering (*deep exhale*)
//savePixmap.setColor(1f,1f,1f,0f)
savePixmap.setColor(0f, 0f, 0f, 0f)
savePixmap.fillRectangle(0, savePixmap.height - titleTopGradStart, savePixmap.width, titleTopGradStart)
// top grad
for (y in titleTopGradStart until titleTopGradEnd) {
val alpha = (y - titleTopGradStart).toFloat() / gradAreaHeight
for (x in 0 until savePixmap.width) {
val col = savePixmap.getPixel(x, savePixmap.height - y)
val blendAlpha = (col.and(0xFF) * alpha).roundToInt()
savePixmap.drawPixel(x, savePixmap.height - y, col.and(0xFFFFFF00.toInt()) or blendAlpha)
}
}
// bottom grad
for (y in titleBottomGradStart until titleBottomGradEnd) {
val alpha = 1f - ((y - titleBottomGradStart).toFloat() / gradAreaHeight)
for (x in 0 until savePixmap.width) {
val col = savePixmap.getPixel(x, savePixmap.height - y)
val blendAlpha = (col.and(0xFF) * alpha).roundToInt()
savePixmap.drawPixel(x, savePixmap.height - y, col.and(0xFFFFFF00.toInt()) or blendAlpha)
}
}
savePixmap.setColor(0f, 0f, 0f, 0f)
savePixmap.fillRectangle(0, 0, savePixmap.width, height - titleBottomGradEnd + 1)
setCameraPosition(batch, camera, 0f, 0f)
val saveTex = TextureRegion(Texture(savePixmap)); saveTex.flip(false, true)
batch.inUse {
batch.draw(saveTex, (width - uiWidth - 10) / 2f, 0f)
// Control help
App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat())
}
saveTex.texture.dispose()
savePixmap.dispose()
batch.begin()
}
else if (mode == MODE_SAVE_MULTIPLE_CHOICES) {
// "The Autosave is more recent than the manual save"
Toolkit.drawTextCentered(batch, App.fontGame, Lang["GAME_MORE_RECENT_AUTOSAVE1"], Toolkit.drawWidth, 0, altSelDrawY)
Toolkit.drawTextCentered(batch, App.fontGame, Lang["GAME_MORE_RECENT_AUTOSAVE2"], Toolkit.drawWidth, 0, altSelDrawY + 24)
// Manual Save Autosave
Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_IO_MANUAL_SAVE"], altSelHdrawW, (Toolkit.drawWidth - altSelDrawW)/2, altSelDrawY + 80)
Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_IO_AUTOSAVE"], altSelHdrawW, Toolkit.drawWidth/2, altSelDrawY + 80)
// draw thumbnail-buttons
loadAutoThumbButton.render(batch, camera)
loadManualThumbButton.render(batch, camera)
}
else if (mode == MODE_SAVE_DAMAGED) {
Toolkit.drawTextCentered(batch, App.fontGame, Lang["ERROR_SAVE_CORRUPTED"], Toolkit.drawWidth, 0, App.scr.height / 2 - 42)
corruptedBackButton.render(batch, camera)
}
if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE || mode == MODE_SAVE_DELETE_CONFIRM) {
deleteCharacterButton.render(batch, camera)
}
if (mode == MODE_SAVE_DELETE_CONFIRM) {
buttonSelectedForDeletion?.render(batch, camera)
confirmCancelButton.render(batch, camera)
confirmDeleteButton.render(batch, camera)
}
if (mode == MODE_SAVE_DELETE_CONFIRM) {
batch.color = Color.WHITE
Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_LABEL_SAVE_WILL_BE_DELETED"], Toolkit.drawWidth, 0, titleTopGradEnd + cellInterval - 46)
Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_LABEL_ARE_YOU_SURE"], Toolkit.drawWidth, 0, titleTopGradEnd + cellInterval + SAVE_CELL_HEIGHT + 36)
}
}
override fun keyDown(keycode: Int): Boolean {
if (this.isVisible && (mode == MODE_SELECT || mode == MODE_SAVE_DELETE)) {
val cells = getCells()
if ((keycode == Input.Keys.UP || keycode == App.getConfigInt("control_key_up")) && scrollTarget > 0) {
scrollFrom = listScroll
scrollTarget -= 1
scrollAnimCounter = 0f
}
else if ((keycode == Input.Keys.DOWN || keycode == App.getConfigInt("control_key_down")) && scrollTarget < cells.size - savesVisible) {
scrollFrom = listScroll
scrollTarget += 1
scrollAnimCounter = 0f
}
}
return true
}
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
printdbg(this, "touchDown mode=$mode")
if (mode == MODE_SAVE_MULTIPLE_CHOICES) {
if (::loadAutoThumbButton.isInitialized) loadAutoThumbButton.touchDown(screenX, screenY, pointer, button)
if (::loadManualThumbButton.isInitialized) loadManualThumbButton.touchDown(screenX, screenY, pointer, button)
}
else if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
getCells().forEach { it.touchDown(screenX, screenY, pointer, button) }
deleteCharacterButton.touchDown(screenX, screenY, pointer, button)
}
else if (mode == MODE_SAVE_DELETE_CONFIRM) {
confirmCancelButton.touchDown(screenX, screenY, pointer, button)
confirmDeleteButton.touchDown(screenX, screenY, pointer, button)
}
else if (mode == MODE_SAVE_DAMAGED) corruptedBackButton.touchDown(screenX, screenY, pointer, button)
return true
}
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
if (mode == MODE_SAVE_MULTIPLE_CHOICES) {
if (::loadAutoThumbButton.isInitialized) loadAutoThumbButton.touchUp(screenX, screenY, pointer, button)
if (::loadManualThumbButton.isInitialized) loadManualThumbButton.touchUp(screenX, screenY, pointer, button)
}
else if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
getCells().forEach { it.touchUp(screenX, screenY, pointer, button) }
deleteCharacterButton.touchUp(screenX, screenY, pointer, button)
}
else if (mode == MODE_SAVE_DELETE_CONFIRM) {
confirmCancelButton.touchUp(screenX, screenY, pointer, button)
confirmDeleteButton.touchUp(screenX, screenY, pointer, button)
}
else if (mode == MODE_SAVE_DAMAGED) corruptedBackButton.touchUp(screenX, screenY, pointer, button)
return true
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
if (this.isVisible && mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
val cells = getCells()
if (amountY <= -1f && scrollTarget > 0) {
scrollFrom = listScroll
scrollTarget -= 1
scrollAnimCounter = 0f
}
else if (amountY >= 1f && scrollTarget < cells.size - savesVisible) {
scrollFrom = listScroll
scrollTarget += 1
scrollAnimCounter = 0f
}
}
return true
}
override fun endClosing(delta: Float) {
super.endClosing(delta)
listScroll = 0
scrollTarget = 0
uiScroll = 0f
}
override fun dispose() {
try { shapeRenderer.dispose() } catch (e: IllegalArgumentException) {}
try { sliderFBO.dispose() } catch (e: IllegalArgumentException) {}
disposablePool.forEach {
try { it.dispose() } catch (e: GdxRuntimeException) {}
}
}
override fun resize(width: Int, height: Int) {
super.resize(width, height)
scrollAreaHeight = height - 2 * App.scr.tvSafeGraphicsHeight - 64
savesVisible = (scrollAreaHeight + cellInterval) / (cellInterval + SAVE_CELL_HEIGHT)
listScroll = 0
scrollTarget = 0
uiScroll = 0f
sliderFBO.dispose()
sliderFBO = FrameBuffer(Pixmap.Format.RGBA8888, uiWidth + 10, height, false)
}
private fun setCameraPosition(batch: SpriteBatch, camera: Camera, newX: Float, newY: Float) {
camera.position.set((-newX + App.scr.halfw).round(), (-newY + App.scr.halfh).round(), 0f)
camera.update()
batch.projectionMatrix = camera.combined
}
}

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.Second
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameactors.AVKey
@@ -47,7 +48,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
private var uiLocked = false
init {
goButton.touchDownListener = { _, _, _, _ ->
goButton.clickOnceListener = { _,_ ->
uiLocked = true
@@ -64,6 +65,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
val savingThread = Thread({
printdbg(this, "Player saving thread fired")
disk.saveMode = 2 // auto, no quick
disk.capacity = 0L
@@ -79,12 +81,17 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
UILoadGovernor.playerDisk = DiskSkimmer(outFile)
// comment above if chargen must send gamers back to the charcters list
printdbg(this, "playerdisk: ${UILoadGovernor.playerDisk?.diskFile?.path}")
}, "TerrarumBasegameNewCharcterSaveThread")
savingThread.start()
// savingThread.start()
// savingThread.join()
remoCon.openUI(UINewWorld(remoCon, savingThread)) // let UINewWorld handle the character file generation
}
backButton.touchDownListener = { _, _, _, _ ->
remoCon.openUI(UILoadDemoSavefiles(remoCon, 0))
backButton.clickOnceListener = { _,_ ->
remoCon.openUI(UILoadSavegame(remoCon))
}
addUIitem(nameInput)
@@ -99,7 +106,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
if (returnedFromChargen) {
returnedFromChargen = false
remoCon.openUI(UILoadDemoSavefiles(remoCon, 1)) // 0 to go back (Terraria's behav), set variables up and 1 to choose world
remoCon.openUI(UILoadSavegame(remoCon))
}
}
@@ -107,7 +114,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
batch.color = Color.WHITE
// ui title
// val titlestr = Lang["CONTEXT_WORLD_NEW"]
// App.fontUITitle.draw(batch, titlestr, drawX + (width - App.fontGame.getWidth(titlestr)).div(2).toFloat(), titleTextPosY.toFloat())
// App.fontUITitle.draw(batch, titlestr, drawX + (width - App.fontUITitle.getWidth(titlestr)).div(2).toFloat(), titleTextPosY.toFloat())
// name/seed input labels

View File

@@ -7,11 +7,8 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.random.HQRNG
import net.torvald.random.XXHash64
import net.torvald.terrarum.App
import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.Second
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.TerrarumIngame.Companion.NEW_WORLD_SIZE
@@ -30,6 +27,12 @@ import net.torvald.terrarum.utils.RandomWordsName
*/
class UINewWorld(val remoCon: UIRemoCon) : UICanvas() {
private var newPlayerCreationThread = Thread {}
constructor(remoCon: UIRemoCon, playerCreationThread: Thread) : this(remoCon) {
newPlayerCreationThread = playerCreationThread
}
private val hugeTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/huge.png")))
private val largeTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/large.png")))
private val normalTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/normal.png")))
@@ -85,9 +88,16 @@ class UINewWorld(val remoCon: UIRemoCon) : UICanvas() {
private val backButton = UIItemTextButton(this, "MENU_LABEL_BACK", drawX + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
private val goButton = UIItemTextButton(this, "MENU_LABEL_CONFIRM_BUTTON", drawX + width/2 + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
init {
goButton.touchDownListener = { _, _, _, _ ->
// printdbg(this, "generate! Size=${sizeSelector.selection}, Name=${nameInput.getTextOrPlaceholder()}, Seed=${seedInput.getTextOrPlaceholder()}")
goButton.clickOnceListener = { _, _ ->
// after the save is complete, proceed to new world generation
newPlayerCreationThread.start()
newPlayerCreationThread.join()
printdbg(this, "generate! Size=${sizeSelector.selection}, Name=${nameInput.getTextOrPlaceholder()}, Seed=${seedInput.getTextOrPlaceholder()}")
val ingame = TerrarumIngame(App.batch)
val player = ReadActor.invoke(UILoadGovernor.playerDisk!!, ByteArray64Reader(UILoadGovernor.playerDisk!!.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET)) as IngamePlayer
@@ -111,8 +121,8 @@ class UINewWorld(val remoCon: UIRemoCon) : UICanvas() {
App.setLoadScreen(loadScreen)
}
backButton.touchDownListener = { _, _, _, _ ->
remoCon.openUI(UILoadDemoSavefiles(remoCon, 1))
backButton.clickOnceListener = { _, _ ->
remoCon.openUI(UILoadSavegame(remoCon))
}
addUIitem(sizeSelector)
@@ -131,7 +141,7 @@ class UINewWorld(val remoCon: UIRemoCon) : UICanvas() {
batch.color = Color.WHITE
// ui title
// val titlestr = Lang["CONTEXT_WORLD_NEW"]
// App.fontUITitle.draw(batch, titlestr, drawX + (width - App.fontGame.getWidth(titlestr)).div(2).toFloat(), titleTextPosY.toFloat())
// App.fontUITitle.draw(batch, titlestr, drawX + (width - App.fontUITitle.getWidth(titlestr)).div(2).toFloat(), titleTextPosY.toFloat())
// draw size previews
val texture = tex[sizeSelector.selection.coerceAtMost(tex.lastIndex)]

View File

@@ -0,0 +1,239 @@
package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.ceilInt
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.ui.*
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import net.torvald.unicode.TIMES
/**
* Created by minjaesong on 2023-06-22.
*/
class UIPerformanceControlPanel(remoCon: UIRemoCon?) : UICanvas() {
private val linegap = 14
private val panelgap = 20
private val rowheight = 20 + linegap
private val h1MarginTop = 16
private val h1MarginBottom = 4
private val options = arrayOf(
arrayOf("", { Lang["MENU_OPTIONS_GAMEPLAY"] }, "h1"),
arrayOf("autosaveinterval", { Lang["MENU_OPTIONS_AUTOSAVE"] + " (${Lang["CONTEXT_TIME_MINUTE_PLURAL"]})" }, "spinnerimul,1,120,1,60000"),
arrayOf("notificationshowuptime", { Lang["MENU_OPTIONS_NOTIFICATION_DISPLAY_DURATION"] + " (${Lang["CONTEXT_TIME_SECOND_PLURAL"]})" }, "spinnerimul,2,10,1,1000"),
arrayOf("", { Lang["MENU_LABEL_JVM_DNT"] }, "h1"),
arrayOf("jvm_xmx", { Lang["MENU_OPTIONS_JVM_HEAP_MAX"] + " (GB)" }, "spinner,2,32,1"),
arrayOf("jvm_extra_cmd", { Lang["MENU_LABEL_EXTRA_JVM_ARGUMENTS"] }, "typein"),
arrayOf("", { "(${Lang["MENU_LABEL_RESTART_REQUIRED"]})" }, "p"),
)
private val optionsYpos = IntArray(options.size + 1)
init {
CommonResourcePool.addToLoadingList("gui_hrule") {
TextureRegionPack(Gdx.files.internal("assets/graphics/gui/hrule.tga"), 216, 20)
}
CommonResourcePool.loadAll()
var akku = 0
options.forEachIndexed { index, row ->
val option = row[2]
if (index > 0 && option == "h1") {
akku += h1MarginTop
}
optionsYpos[index] = akku
akku += when (option) {
"h1" -> rowheight + h1MarginBottom
else -> rowheight
}
}
optionsYpos[optionsYpos.lastIndex] = akku
}
override var width = 560
override var height = optionsYpos.last()
private val hrule = CommonResourcePool.getAsTextureRegionPack("gui_hrule")
private val spinnerWidth = 140
private val typeinWidth = 240
private val drawX = (Toolkit.drawWidth - width) / 2
private val drawY = (App.scr.height - height) / 2
// @return Pair of <UIItem, Init job for the item>
private fun makeButton(args: String, x: Int, y: Int, optionName: String): Pair<UIItem, (UIItem, String) -> Unit> {
return if (args.startsWith("h1") || args.startsWith("p")) {
(object : UIItem(this, x, y) {
override val width = 1
override val height = 1
override fun dispose() {}
}) to { _, _ -> }
}
else if (args.startsWith("toggle")) {
UIItemToggleButton(this, x, y, spinnerWidth, App.getConfigBoolean(optionName)) to { it: UIItem, optionStr: String ->
(it as UIItemToggleButton).clickOnceListener = { _, _ ->
it.toggle()
App.setConfig(optionStr, it.getStatus())
}
}
}
else if (args.startsWith("spinner,")) {
val arg = args.split(',')
UIItemSpinner(this, x, y, App.getConfigInt(optionName), arg[1].toInt(), arg[2].toInt(), arg[3].toInt(), spinnerWidth, numberToTextFunction = { "${it.toLong()}" }) to { it: UIItem, optionStr: String ->
(it as UIItemSpinner).selectionChangeListener = {
App.setConfig(optionStr, it)
}
}
}
else if (args.startsWith("spinnerd,")) {
val arg = args.split(',')
UIItemSpinner(this, x, y, App.getConfigDouble(optionName), arg[1].toDouble(), arg[2].toDouble(), arg[3].toDouble(), spinnerWidth, numberToTextFunction = { "${((it as Double)*100).toInt()}%" }) to { it: UIItem, optionStr: String ->
(it as UIItemSpinner).selectionChangeListener = {
App.setConfig(optionStr, it)
}
}
}
else if (args.startsWith("spinnerimul,")) {
val arg = args.split(',')
val mult = arg[4].toInt()
UIItemSpinner(this, x, y, App.getConfigInt(optionName) / mult, arg[1].toInt(), arg[2].toInt(), arg[3].toInt(), spinnerWidth, numberToTextFunction = { "${it.toLong()}" }) to { it: UIItem, optionStr: String ->
(it as UIItemSpinner).selectionChangeListener = {
App.setConfig(optionStr, it.toInt() * mult)
}
}
}
else if (args.startsWith("typeinint")) {
// val arg = args.split(',') // args: none
UIItemTextLineInput(this, x, y, spinnerWidth,
defaultValue = { "${App.getConfigInt(optionName)}" },
maxLen = InputLenCap(4, InputLenCap.CharLenUnit.CODEPOINTS),
keyFilter = { it.headkey in Input.Keys.NUM_0..Input.Keys.NUM_9 || it.headkey == Input.Keys.BACKSPACE }
) to { it: UIItem, optionStr: String ->
(it as UIItemTextLineInput).textCommitListener = {
App.setConfig(optionStr, it.toInt()) // HAXXX!!!
}
}
}
else if (args.startsWith("typeinres")) {
val keyWidth = optionName.substringBefore(',')
val keyHeight = optionName.substringAfter(',')
UIItemTextLineInput(this, x, y, spinnerWidth,
defaultValue = { "${App.getConfigInt(keyWidth)}x${App.getConfigInt(keyHeight)}" },
maxLen = InputLenCap(9, InputLenCap.CharLenUnit.CODEPOINTS),
keyFilter = { it.headkey == Input.Keys.ENTER || it.headkey == Input.Keys.BACKSPACE || it.character?.matches(Regex("[0-9xX]")) == true },
alignment = UIItemTextButton.Companion.Alignment.CENTRE
) to { it: UIItem, optionStr: String ->
(it as UIItemTextLineInput).textCommitListener = { text ->
val text = text.lowercase()
if (text.matches(Regex("""[0-9]+x[0-9]+"""))) {
it.markAsNormal()
val width = text.substringBefore('x').toInt()
val height = text.substringAfter('x').toInt()
App.setConfig(keyWidth, width)
App.setConfig(keyHeight, height)
}
else it.markAsInvalid()
}
}
}
else if (args.startsWith("typein")) {
//args: none
UIItemTextLineInput(this, x, y, typeinWidth, defaultValue = { App.getConfigString(optionName) }) to { it: UIItem, optionStr: String ->
(it as UIItemTextLineInput).textCommitListener = {
App.setConfig(optionStr, it)
}
}
}
else throw IllegalArgumentException(args)
}
private val optionControllers: List<Pair<UIItem, (UIItem, String) -> Unit>> = options.mapIndexed { index, strings ->
makeButton(options[index][2] as String,
drawX + width / 2 + panelgap,
drawY - 2 + optionsYpos[index],
options[index][0] as String
)
}
init {
optionControllers.forEachIndexed { i, it ->
it.second.invoke(it.first, options[i][0] as String)
addUIitem(it.first)
}
}
override fun updateUI(delta: Float) {
uiItems.forEach { it.update(delta) }
}
override fun renderUI(batch: SpriteBatch, camera: Camera) {
/*batch.color = Toolkit.Theme.COL_INACTIVE
Toolkit.drawBoxBorder(batch, drawX, drawY, width, height)
batch.color = CELL_COL
Toolkit.fillArea(batch, drawX, drawY, width, height)*/
options.forEachIndexed { index, strings ->
val mode = strings[2]
val font = if (mode == "h1") App.fontUITitle else App.fontGame
val label = (strings[1] as () -> String).invoke()
val labelWidth = font.getWidth(label)
batch.color = when (mode) {
"h1" -> Toolkit.Theme.COL_MOUSE_UP
"p" -> Color.LIGHT_GRAY
else -> Color.WHITE
}
val xpos = if (mode == "p" || mode == "h1")
drawX + (width - labelWidth)/2 // centre-aligned
else
drawX + width/2 - panelgap - labelWidth // right aligned at the middle of the panel, offsetted by panelgap
font.draw(batch, label, xpos.toFloat(), drawY + optionsYpos[index] - 2f)
// draw hrule
if (mode == "h1") {
val ruleWidth = ((width - 24 - labelWidth) / 2).toFloat()
batch.draw(hrule.get(0,0), xpos - 24f - ruleWidth, drawY + optionsYpos[index].toFloat(), ruleWidth, hrule.tileH.toFloat())
batch.draw(hrule.get(0,1), xpos + 24f + labelWidth, drawY + optionsYpos[index].toFloat(), ruleWidth, hrule.tileH.toFloat())
}
}
uiItems.forEach { it.render(batch, camera) }
if (App.getConfigBoolean("fx_streamerslayout")) {
val xstart = App.scr.width - App.scr.chatWidth
batch.color = Color(0x00f8ff_40)
Toolkit.fillArea(batch, xstart + 1, 1, App.scr.chatWidth - 2, App.scr.height - 2)
batch.color = Toolkit.Theme.COL_MOUSE_UP
Toolkit.drawBoxBorder(batch, xstart + 1, 1, App.scr.chatWidth - 2, App.scr.height - 2)
val overlayResTxt = "${(App.scr.chatWidth * App.scr.magn).ceilInt()}$TIMES${App.scr.windowH}"
App.fontGame.draw(batch, overlayResTxt,
(xstart + (App.scr.chatWidth - App.fontGame.getWidth(overlayResTxt)) / 2).toFloat(),
((App.scr.height - App.fontGame.lineHeight) / 2).toFloat()
)
}
}
override fun dispose() {
}
}

View File

@@ -49,7 +49,7 @@ class UIQuickslotPie : UICanvas() {
// update controls
if (handler.isOpened || handler.isOpening) {
val cursorPos = Vector2(Terrarum.mouseScreenX.toDouble(), Terrarum.mouseScreenY.toDouble())
val centre = Vector2(Toolkit.drawWidth / 2.0, App.scr.halfh.toDouble())
val centre = Vector2(Toolkit.hdrawWidth.toDouble(), App.scr.halfh.toDouble())
val deg = -(centre - cursorPos).direction.toFloat()
selection = Math.round(deg * slotCount / FastMath.TWO_PI)

View File

@@ -8,6 +8,7 @@ import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameitems.GameItem
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.getWidthOfCells
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.unicode.getKeycapPC
@@ -23,7 +24,7 @@ internal class UIStorageChest : UICanvas(
lateinit var chestInventory: FixtureInventory
lateinit var chestNameFun: () -> String
override var width = App.scr.width
override var width = Toolkit.drawWidth
override var height = App.scr.height
private val negotiator = object : InventoryTransactionNegotiator() {
@@ -49,12 +50,12 @@ internal class UIStorageChest : UICanvas(
private var encumbrancePerc = 0f
private var isEncumbered = false
private var halfSlotOffset = (UIItemInventoryElemSimple.height + UIItemInventoryItemGrid.listGap) / 2
private var halfSlotOffset = (UIItemInventoryElemSimple.height + UIItemInventoryItemGrid.listGap * 2) / 2
init {
catBar = UIItemInventoryCatBar(
this,
(App.scr.width - UIInventoryFull.catBarWidth) / 2,
(width - UIInventoryFull.catBarWidth) / 2,
42 - UIInventoryFull.YPOS_CORRECTION + (App.scr.height - UIInventoryFull.internalHeight) / 2,
UIInventoryFull.internalWidth,
UIInventoryFull.catBarWidth,
@@ -65,42 +66,46 @@ internal class UIStorageChest : UICanvas(
this,
catBar,
{ getFixtureInventory() },
UIInventoryFull.INVENTORY_CELLS_OFFSET_X() - halfSlotOffset,
Toolkit.hdrawWidth - getWidthOfCells(6) - halfSlotOffset,
UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(),
6, UIInventoryFull.CELLS_VRT,
drawScrollOnRightside = false,
drawWallet = false,
keyDownFun = { _, _, _, _, _ -> Unit },
touchDownFun = { gameItem, amount, _, _, _ ->
if (gameItem != null) {
negotiator.reject(getFixtureInventory(), getPlayerInventory(), gameItem, amount)
touchDownFun = { gameItem, amount, button, _, _ ->
if (button == App.getConfigInt("config_mouseprimary")) {
if (gameItem != null) {
negotiator.reject(getFixtureInventory(), getPlayerInventory(), gameItem, amount)
}
itemListUpdate()
}
itemListUpdate()
}
)
// make grid mode buttons work together
itemListChest.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) }
itemListChest.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) }
itemListChest.navRemoCon.listButtonListener = { _,_ -> setCompact(false) }
itemListChest.navRemoCon.gridButtonListener = { _,_ -> setCompact(true) }
itemListPlayer = UIItemInventoryItemGrid(
this,
catBar,
{ INGAME.actorNowPlaying!!.inventory }, // literally a player's inventory
UIInventoryFull.INVENTORY_CELLS_OFFSET_X() - halfSlotOffset + (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 7,
Toolkit.hdrawWidth + halfSlotOffset,
UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(),
6, UIInventoryFull.CELLS_VRT,
drawScrollOnRightside = true,
drawWallet = false,
keyDownFun = { _, _, _, _, _ -> Unit },
touchDownFun = { gameItem, amount, _, _, _ ->
if (gameItem != null) {
negotiator.accept(getPlayerInventory(), getFixtureInventory(), gameItem, amount)
touchDownFun = { gameItem, amount, button, _, _ ->
if (button == App.getConfigInt("config_mouseprimary")) {
if (gameItem != null) {
negotiator.accept(getPlayerInventory(), getFixtureInventory(), gameItem, amount)
}
itemListUpdate()
}
itemListUpdate()
}
)
itemListPlayer.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) }
itemListPlayer.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) }
itemListPlayer.navRemoCon.listButtonListener = { _,_ -> setCompact(false) }
itemListPlayer.navRemoCon.gridButtonListener = { _,_ -> setCompact(true) }
handler.allowESCtoClose = true
@@ -132,14 +137,14 @@ internal class UIStorageChest : UICanvas(
private fun setCompact(yes: Boolean) {
itemListChest.isCompactMode = yes
itemListChest.gridModeButtons[0].highlighted = !yes
itemListChest.gridModeButtons[1].highlighted = yes
itemListChest.navRemoCon.gridModeButtons[0].highlighted = !yes
itemListChest.navRemoCon.gridModeButtons[1].highlighted = yes
itemListChest.itemPage = 0
itemListChest.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
itemListPlayer.isCompactMode = yes
itemListPlayer.gridModeButtons[0].highlighted = !yes
itemListPlayer.gridModeButtons[1].highlighted = yes
itemListPlayer.navRemoCon.gridModeButtons[0].highlighted = !yes
itemListPlayer.navRemoCon.gridModeButtons[1].highlighted = yes
itemListPlayer.itemPage = 0
itemListPlayer.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
@@ -161,7 +166,7 @@ internal class UIStorageChest : UICanvas(
if (openingClickLatched && !Terrarum.mouseDown) openingClickLatched = false
}
private val thisOffsetX = UIInventoryFull.INVENTORY_CELLS_OFFSET_X() - halfSlotOffset
private val thisOffsetX = Toolkit.hdrawWidth - getWidthOfCells(6) - halfSlotOffset
private val thisOffsetX2 = thisOffsetX + (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 7
private val thisOffsetY = UIInventoryFull.INVENTORY_CELLS_OFFSET_Y()
private val cellsWidth = (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 6 - UIItemInventoryItemGrid.listGap
@@ -174,7 +179,7 @@ internal class UIStorageChest : UICanvas(
override fun renderUI(batch: SpriteBatch, camera: Camera) {
// background fill
UIInventoryFull.drawBackground(batch, handler.opacity)
UIInventoryFull.drawBackground(batch, 1f)
// UI items
batch.color = Color.WHITE
@@ -232,19 +237,23 @@ internal class UIStorageChest : UICanvas(
}
override fun doOpening(delta: Float) {
super.doOpening(delta)
INGAME.pause()
INGAME.setTooltipMessage(null)
}
override fun doClosing(delta: Float) {
super.doClosing(delta)
INGAME.resume()
INGAME.setTooltipMessage(null)
}
override fun endOpening(delta: Float) {
super.endOpening(delta)
}
override fun endClosing(delta: Float) {
super.endClosing(delta)
UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required!
}

View File

@@ -88,7 +88,7 @@ class UITitleModules(val remoCon: UIRemoCon) : UICanvas() {
savegamesCount += 1
}
catch (e: Throwable) {
System.err.println("[UILoadDemoSavefiles] Error while loading module info for '$s'")
System.err.println("[UITitleModules] Error while loading module info for '$s'")
e.printStackTrace()
}
}
@@ -192,7 +192,7 @@ class UITitleModules(val remoCon: UIRemoCon) : UICanvas() {
// draw texts
val loadGameTitleStr = Lang["MENU_MODULES"]// + "$EMDASH$hash"
// "Game Load"
App.fontUITitle.draw(batch, loadGameTitleStr, (width - App.fontGame.getWidth(loadGameTitleStr)).div(2).toFloat(), titleTextPosY.toFloat())
App.fontUITitle.draw(batch, loadGameTitleStr, (width - App.fontUITitle.getWidth(loadGameTitleStr)).div(2).toFloat(), titleTextPosY.toFloat())
// Control help
App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat())
}

View File

@@ -12,12 +12,13 @@ object UITitleRemoConYaml {
* The class must be the UICanvas
*/
val menuBase = """
- MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadDemoSavefiles
- MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame
- MENU_OPTIONS
- MENU_LABEL_GRAPHICS : net.torvald.terrarum.modulebasegame.ui.UIGraphicsControlPanel
- MENU_OPTIONS_CONTROLS : net.torvald.terrarum.modulebasegame.ui.UIKeyboardControlPanel
- MENU_LABEL_IME : net.torvald.terrarum.modulebasegame.ui.UIIMEConfig
- MENU_LABEL_LANGUAGE : net.torvald.terrarum.modulebasegame.ui.UITitleLanguage
- GAME_GENRE_MISC : net.torvald.terrarum.modulebasegame.ui.UIPerformanceControlPanel
- MENU_MODULES : net.torvald.terrarum.ModOptionsHost
- MENU_LABEL_RETURN+WRITETOCONFIG
- MENU_MODULES : net.torvald.terrarum.modulebasegame.ui.UITitleModules
@@ -25,6 +26,7 @@ object UITitleRemoConYaml {
- MENU_LABEL_CREDITS
- MENU_LABEL_COPYRIGHT : net.torvald.terrarum.modulebasegame.ui.UITitleCredits
- MENU_CREDIT_GPL_DNT : net.torvald.terrarum.modulebasegame.ui.UITitleGPL3
- MENU_LABEL_SYSTEM_INFO : net.torvald.terrarum.modulebasegame.ui.UISystemInfo
- MENU_LABEL_RETURN
- MENU_LABEL_QUIT
"""

View File

@@ -43,4 +43,5 @@ open class UITitleWallOfText(private val text: List<String>) : UICanvas() {
}
class UITitleCredits(val remoCon: UIRemoCon) : UITitleWallOfText(CreditSingleton.credit)
class UITitleGPL3(val remoCon: UIRemoCon) : UITitleWallOfText(CreditSingleton.gpl3)
class UITitleGPL3(val remoCon: UIRemoCon) : UITitleWallOfText(CreditSingleton.gpl3)
class UISystemInfo(val remoCon: UIRemoCon) : UITitleWallOfText(CreditSingleton.systeminfo)

View File

@@ -56,10 +56,11 @@ class UITooltip : UICanvas() {
batch.color = tooltipBackCol
Toolkit.drawBaloon(batch,
tooltipX - textMarginX,
tooltipY,
tooltipW,
font.lineHeight * msgBuffer.size
tooltipX - textMarginX,
tooltipY,
tooltipW,
font.lineHeight * msgBuffer.size,
Notification.OPACITY
)
batch.color = tooltipForeCol

View File

@@ -3,18 +3,22 @@ package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.jme3.math.FastMath
import net.torvald.terrarum.*
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureWorldPortal
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.INVENTORY_CELLS_OFFSET_Y
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.YPOS_CORRECTION
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.drawBackground
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.internalHeight
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.internalWidth
import net.torvald.terrarum.serialise.toAscii85
import net.torvald.terrarum.ui.*
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import net.torvald.unicode.getKeycapConsole
import net.torvald.unicode.getKeycapPC
import java.util.UUID
/**
* Structure:
@@ -34,60 +38,45 @@ class UIWorldPortal : UICanvas(
override var width: Int = Toolkit.drawWidth
override var height: Int = App.scr.height
internal lateinit var host: FixtureWorldPortal
val controlHelpHeight = App.fontGame.lineHeight
private var panelTransitionLocked = false
fun lockTransition() {
panelTransitionLocked = true
}
fun unlockTransition() {
panelTransitionLocked = false
}
fun requestTransition(target: Int) = transitionPanel.requestTransition(target)
val catBar = UIItemWorldPortalTopBar(
this,
0,
42 - YPOS_CORRECTION + (App.scr.height - internalHeight) / 2,
) { i -> if (!panelTransitionLocked) requestTransition(i) }
private val SP = "\u3000 "
val portalListingControlHelp: String
get() = if (App.environment == RunningEnvironment.PC)
"${getKeycapPC(App.getConfigInt("control_key_up"))}${getKeycapPC(App.getConfigInt("control_key_down"))}" +
" ${Lang["MENU_CONTROLS_SCROLL"]}" +
"$SP${getKeycapPC(App.getConfigInt("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}"
"${getKeycapPC(App.getConfigInt("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}"
else
"${getKeycapConsole('R')} ${Lang["MENU_CONTROLS_SCROLL"]}" +
"$SP${App.gamepadLabelStart} ${Lang["GAME_ACTION_CLOSE"]}" +
"${App.gamepadLabelStart} ${Lang["GAME_ACTION_CLOSE"]}" +
"$SP${App.gamepadLabelLT} ${Lang["GAME_WORLD_SEARCH"]}" +
"$SP${App.gamepadLabelRT} ${Lang["GAME_INVENTORY"]}"
private val transitionalSearch = UIWorldPortalSearch(this)
private val transitionalListing = UIWorldPortalListing(this)
private val transitionalCargo = UIWorldPortalCargo(this)
val transitionalSearch = UIWorldPortalSearch(this)
val transitionalListing = UIWorldPortalListing(this)
// val transitionalCargo = UIWorldPortalCargo(this)
private val transitionPanel = UIItemHorizontalFadeSlide(
this,
(width - internalWidth) / 2,
INVENTORY_CELLS_OFFSET_Y(),
width,
App.scr.height,
1f,
transitionalSearch, transitionalListing, transitionalCargo
0f,
transitionalListing, transitionalSearch
)
/**
* Called by:
* - "Search" button on UIWorldPortalListing
* - "Cancel" button on UIWorldPortalSearch
*/
fun requestTransition(target: Int) = transitionPanel.requestTransition(target)
init {
addUIitem(catBar)
addUIitem(transitionPanel)
}
internal var xEnd = (width + internalWidth).div(2).toFloat()
@@ -99,57 +88,133 @@ class UIWorldPortal : UICanvas(
override fun updateUI(delta: Float) {
transitionPanel.update(delta)
}
override fun renderUI(batch: SpriteBatch, camera: Camera) {
drawBackground(batch, handler.opacity)
drawBackground(batch, 1f)
// UI items
catBar.render(batch, camera)
transitionPanel.render(batch, camera)
}
private fun addWorldToPlayersDict(uuid: UUID) {
val uuidstr = uuid.toAscii85()
INGAME.actorNowPlaying?.let {
val avList = (it.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(',').filter { it.isNotBlank() }.toMutableList()
if (!avList.contains(uuidstr)) {
avList.add(uuidstr)
it.actorValue[AVKey.WORLD_PORTAL_DICT] = avList.joinToString(",")
}
}
}
private fun cleanUpWorldDict() {
// remove dupes, etc
INGAME.actorNowPlaying?.let {
val avList = (it.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(',').filter { it.isNotBlank() }.toSet()
it.actorValue[AVKey.WORLD_PORTAL_DICT] = avList.joinToString(",")
}
}
override fun show() {
super.show()
transitionPanel.forcePosition(0)
transitionPanel.show()
INGAME.setTooltipMessage(null)
// add current world to the player's worldportaldict
addWorldToPlayersDict(INGAME.world.worldIndex)
cleanUpWorldDict()
}
override fun hide() {
transitionPanel.hide()
}
override fun dispose() {
catBar.dispose()
}
fun resetUI() {
transitionPanel.dispose()
}
override fun doOpening(delta: Float) {
super.doOpening(delta)
resetUI()
transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
INGAME.pause()
INGAME.setTooltipMessage(null)
}
override fun doClosing(delta: Float) {
super.doClosing(delta)
transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
INGAME.resume()
INGAME.setTooltipMessage(null)
}
override fun endOpening(delta: Float) {
super.endOpening(delta)
transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required!
}
override fun endClosing(delta: Float) {
super.endClosing(delta)
resetUI()
transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required!
}
override fun inputStrobed(e: TerrarumKeyboardEvent) {
super.inputStrobed(e)
transitionPanel.uis.forEach { it.inputStrobed(e) }
}
override fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean {
super.touchDragged(screenX, screenY, pointer)
transitionPanel.uis.forEach { it.touchDragged(screenX, screenY, pointer) }
return true
}
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
super.touchDown(screenX, screenY, pointer, button)
transitionPanel.uis.forEach { it.touchDown(screenX, screenY, pointer, button) }
return true
}
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
super.touchUp(screenX, screenY, pointer, button)
transitionPanel.uis.forEach { it.touchUp(screenX, screenY, pointer, button) }
return true
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
super.scrolled(amountX, amountY)
transitionPanel.uis.forEach { it.scrolled(amountX, amountY) }
return true
}
override fun keyDown(keycode: Int): Boolean {
super.keyDown(keycode)
transitionPanel.uis.forEach { it.keyDown(keycode) }
return true
}
override fun keyUp(keycode: Int): Boolean {
super.keyUp(keycode)
transitionPanel.uis.forEach { it.keyUp(keycode) }
return true
}
override fun keyTyped(character: Char): Boolean {
super.keyTyped(character)
transitionPanel.uis.forEach { it.keyTyped(character) }
return true
}
override fun resize(width: Int, height: Int) {
super.resize(width, height)
transitionPanel.uis.forEach { it.resize(width, height) }
}
}
class UIItemWorldPortalTopBar(
@@ -171,7 +236,7 @@ class UIItemWorldPortalTopBar(
private val genericIcons: TextureRegionPack = CommonResourcePool.getAsTextureRegionPack("inventory_category")
private val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons")
private val catIconImages = listOf(
/*private val catIconImages = listOf(
icons.get(0, 0),
genericIcons.get(16,0),
icons.get(1, 0),
@@ -182,15 +247,21 @@ class UIItemWorldPortalTopBar(
"CONTEXT_WORLD_SEARCH",
"",
"CONTEXT_WORLD_LIST",
"GAME_INVENTORY",
"",
)
"GAME_INVENTORY",
)*/
private val buttonGapSize = 120
private val highlighterYPos = icons.tileH + 4
var selection = 2
var selectedPanel = 2; private set
private val buttons = Array<UIItemImageButton>(5) {
/** (oldIndex: Int?, newIndex: Int) -> Unit
* Indices are raw index. That is, not re-arranged. */
var selectionChangeListener: ((Int?, Int) -> Unit)? = null
private var transitionFired = false
/*private val buttons = Array<UIItemImageButton>(5) {
val xoff = if (it == 1) -32 else if (it == 3) 32 else 0
UIItemImageButton(
parentUI,
@@ -207,17 +278,41 @@ class UIItemWorldPortalTopBar(
)
}
private val workingButtons = arrayOf(0,2,4)*/
override fun update(delta: Float) {
super.update(delta)
/*workingButtons.forEach { buttons[it].update(delta) }
// transition stuffs
workingButtons.filter { buttons[it].mousePushed }.firstOrNull()?.let { pushedButton ->
if (selectedPanel != pushedButton) transitionFired = true
selectedPanel = pushedButton
workingButtons.forEach { i ->
buttons[i].highlighted = i == pushedButton
}
}*/
if (transitionFired) {
transitionFired = false
panelTransitionReqFun(selectedPanel)
}
}
override fun render(batch: SpriteBatch, camera: Camera) {
super.render(batch, camera)
// button
buttons.forEach { it.render(batch, camera) }
/*buttons.forEach { it.render(batch, camera) }
// label
batch.color = Color.WHITE
val text = Lang[catIconLabels[selection]]
App.fontGame.draw(batch, text, buttons[selection].posX + 10 - (App.fontGame.getWidth(text) / 2), posY + highlighterYPos + 4)
val text = Lang[catIconLabels[selectedPanel]]
App.fontGame.draw(batch, text, buttons[selectedPanel].posX + 10 - (App.fontGame.getWidth(text) / 2), posY + highlighterYPos + 4)
*/
blendNormalStraightAlpha(batch)

View File

@@ -1,25 +1,252 @@
package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App
import net.torvald.terrarum.*
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameitems.GameItem
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.unicode.getKeycapPC
class UIWorldPortalCargo(val full: UIWorldPortal) : UICanvas() {
class UIWorldPortalCargo(val full: UIWorldPortal) : UICanvas(), HasInventory {
override var width: Int = Toolkit.drawWidth
override var height: Int = App.scr.height
override fun updateUI(delta: Float) {
TODO("Not yet implemented")
lateinit var chestInventory: FixtureInventory
lateinit var chestNameFun: () -> String
private val negotiator = object : InventoryTransactionNegotiator() {
override fun accept(player: FixtureInventory, fixture: FixtureInventory, item: GameItem, amount: Long) {
player.remove(item, amount)
fixture.add(item, amount)
}
override fun reject(fixture: FixtureInventory, player: FixtureInventory, item: GameItem, amount: Long) {
fixture.remove(item, amount)
player.add(item, amount)
}
}
override fun getNegotiator() = negotiator
override fun getFixtureInventory(): FixtureInventory = chestInventory
override fun getPlayerInventory(): FixtureInventory = INGAME.actorNowPlaying!!.inventory
private val catBar: UIItemInventoryCatBar
private val itemListChest: UIItemInventoryItemGrid
private val itemListPlayer: UIItemInventoryItemGrid
private var encumbrancePerc = 0f
private var isEncumbered = false
private var halfSlotOffset = (UIItemInventoryElemSimple.height + UIItemInventoryItemGrid.listGap * 2) / 2
init {
catBar = UIItemInventoryCatBar(
this,
(width - UIInventoryFull.catBarWidth) / 2,
42 - UIInventoryFull.YPOS_CORRECTION + (App.scr.height - UIInventoryFull.internalHeight) / 2,
UIInventoryFull.internalWidth,
UIInventoryFull.catBarWidth,
false
)
catBar.selectionChangeListener = { old, new -> itemListUpdate() }
itemListChest = UIItemInventoryItemGrid(
this,
catBar,
{ getFixtureInventory() },
Toolkit.hdrawWidth - UIInventoryFull.getWidthOfCells(6) - halfSlotOffset,
UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(),
6, UIInventoryFull.CELLS_VRT,
drawScrollOnRightside = false,
drawWallet = false,
keyDownFun = { _, _, _, _, _ -> Unit },
touchDownFun = { gameItem, amount, button, _, _ ->
if (button == App.getConfigInt("config_mouseprimary")) {
if (gameItem != null) {
negotiator.reject(getFixtureInventory(), getPlayerInventory(), gameItem, amount)
}
itemListUpdate()
}
}
)
// make grid mode buttons work together
itemListChest.navRemoCon.listButtonListener = { _,_ -> setCompact(false) }
itemListChest.navRemoCon.gridButtonListener = { _,_ -> setCompact(true) }
itemListPlayer = UIItemInventoryItemGrid(
this,
catBar,
{ INGAME.actorNowPlaying!!.inventory }, // literally a player's inventory
Toolkit.hdrawWidth + halfSlotOffset,
UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(),
6, UIInventoryFull.CELLS_VRT,
drawScrollOnRightside = true,
drawWallet = false,
keyDownFun = { _, _, _, _, _ -> Unit },
touchDownFun = { gameItem, amount, button, _, _ ->
if (button == App.getConfigInt("config_mouseprimary")) {
if (gameItem != null) {
negotiator.accept(getPlayerInventory(), getFixtureInventory(), gameItem, amount)
}
itemListUpdate()
}
}
)
itemListPlayer.navRemoCon.listButtonListener = { _,_ -> setCompact(false) }
itemListPlayer.navRemoCon.gridButtonListener = { _,_ -> setCompact(true) }
handler.allowESCtoClose = true
addUIitem(itemListChest)
addUIitem(itemListPlayer)
}
private var openingClickLatched = false
override fun show() {
itemListPlayer.getInventory = { INGAME.actorNowPlaying!!.inventory }
itemListUpdate()
openingClickLatched = Terrarum.mouseDown
UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null)
}
private fun itemListUpdate() {
itemListChest.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
itemListPlayer.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
encumbrancePerc = getPlayerInventory().capacity.toFloat() / getPlayerInventory().maxCapacity
isEncumbered = getPlayerInventory().isEncumbered
}
private fun setCompact(yes: Boolean) {
itemListChest.isCompactMode = yes
itemListChest.navRemoCon.gridModeButtons[0].highlighted = !yes
itemListChest.navRemoCon.gridModeButtons[1].highlighted = yes
itemListChest.itemPage = 0
itemListChest.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
itemListPlayer.isCompactMode = yes
itemListPlayer.navRemoCon.gridModeButtons[0].highlighted = !yes
itemListPlayer.navRemoCon.gridModeButtons[1].highlighted = yes
itemListPlayer.itemPage = 0
itemListPlayer.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
itemListUpdate()
}
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
if (!openingClickLatched) {
return super.touchDown(screenX, screenY, pointer, button)
}
return false
}
override fun updateUI(delta: Float) {
catBar.update(delta)
itemListChest.update(delta)
itemListPlayer.update(delta)
if (openingClickLatched && !Terrarum.mouseDown) openingClickLatched = false
}
private val thisOffsetX = Toolkit.hdrawWidth - UIInventoryFull.getWidthOfCells(6) - halfSlotOffset
private val thisOffsetX2 = thisOffsetX + (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 7
private val thisOffsetY = UIInventoryFull.INVENTORY_CELLS_OFFSET_Y()
private val cellsWidth = (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 6 - UIItemInventoryItemGrid.listGap
private val controlHelp: String
get() = if (App.environment == RunningEnvironment.PC)
"${getKeycapPC(App.getConfigInt("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}"
else
"${App.gamepadLabelStart} ${Lang["GAME_ACTION_CLOSE"]} "
override fun renderUI(batch: SpriteBatch, camera: Camera) {
TODO("Not yet implemented")
// background fill
UIInventoryFull.drawBackground(batch, 1f)
// UI items
batch.color = Color.WHITE
catBar.render(batch, camera)
itemListChest.render(batch, camera)
itemListPlayer.render(batch, camera)
blendNormalStraightAlpha(batch)
// encumbrance meter
val encumbranceText = Lang["GAME_INVENTORY_ENCUMBRANCE"]
val chestName = chestNameFun()
val playerName = INGAME.actorNowPlaying!!.actorValue.getAsString(AVKey.NAME).orEmpty().let { it.ifBlank { Lang["GAME_INVENTORY"] } }
val encumbBarXPos = itemListPlayer.posX + itemListPlayer.width - UIInventoryCells.weightBarWidth + 36
val encumbBarTextXPos = encumbBarXPos - 6 - App.fontGame.getWidth(encumbranceText)
val yEnd = -UIInventoryFull.YPOS_CORRECTION + (App.scr.height + UIInventoryFull.internalHeight).div(2).toFloat() // directly copied from UIInventoryFull.yEnd
val encumbBarYPos = yEnd - 20 + 3 // dunno why but extra 3 px is needed
val encumbCol = UIItemInventoryCellCommonRes.getHealthMeterColour(1f - encumbrancePerc, 0f, 1f)
val encumbBack = encumbCol mul UIItemInventoryCellCommonRes.meterBackDarkening
// encumbrance bar background
batch.color = encumbBack
Toolkit.fillArea(
batch,
encumbBarXPos,
encumbBarYPos,
UIInventoryCells.weightBarWidth,
UIInventoryFull.controlHelpHeight - 6f
)
// encumbrance bar
batch.color = encumbCol
Toolkit.fillArea(
batch,
encumbBarXPos, encumbBarYPos,
if (getPlayerInventory().capacityMode == FixtureInventory.CAPACITY_MODE_NO_ENCUMBER)
1f
else // make sure 1px is always be seen
minOf(UIInventoryCells.weightBarWidth, maxOf(1f, UIInventoryCells.weightBarWidth * encumbrancePerc)),
UIInventoryFull.controlHelpHeight - 6f
)
// chest name text
batch.color = Color.WHITE
App.fontGame.draw(batch, chestName, thisOffsetX + (cellsWidth - App.fontGame.getWidth(chestName)) / 2, thisOffsetY - 30)
App.fontGame.draw(batch, playerName, thisOffsetX2 + (cellsWidth - App.fontGame.getWidth(playerName)) / 2, thisOffsetY - 30)
// control hint
App.fontGame.draw(batch, controlHelp, thisOffsetX - 34f, encumbBarYPos - 3)
// encumb text
batch.color = Color.WHITE
App.fontGame.draw(batch, encumbranceText, encumbBarTextXPos, encumbBarYPos - 3f)
}
override fun doOpening(delta: Float) {
INGAME.pause()
INGAME.setTooltipMessage(null)
}
override fun doClosing(delta: Float) {
INGAME.resume()
INGAME.setTooltipMessage(null)
}
override fun endOpening(delta: Float) {
}
override fun endClosing(delta: Float) {
UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required!
}
override fun dispose() {
TODO("Not yet implemented")
}
}

View File

@@ -8,8 +8,11 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureWorldPortal
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.INVENTORY_CELLS_OFFSET_Y
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.getCellCountVertically
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
@@ -28,8 +31,16 @@ import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.zip.GZIPInputStream
import kotlin.math.ceil
/**
*
* "Teleport" button sets the destination and makes the portal surface to 'glow' to indicate the teleportation is ready.
* Teleportation is initiated with a rising edge of a logic signal.
*
* "New World" sets the parameter of the new world, and make the new (not yet generated) world as the teleportation target.
* THe world generation will be done when the "teleportation" is on going.
*
* Created by minjaesong on 2023-05-19.
*/
class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
@@ -47,31 +58,67 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
private val textAreaW = thumbw - 32
private val thumbh = 252
private val hx = Toolkit.drawWidth.div(2)
private val y = INVENTORY_CELLS_OFFSET_Y() + 1
private val y = INVENTORY_CELLS_OFFSET_Y() + 1 - 34
private val listCount = getCellCountVertically(UIItemWorldCellsSimple.height, gridGap)
private val listHeight = UIItemWorldCellsSimple.height + (listCount - 1) * (UIItemWorldCellsSimple.height + gridGap)
private val memoryGaugeWidth = textAreaW
private val deleteButtonWidth = (thumbw - gridGap) / 2
private val buttonDeleteWorld = UIItemTextButton(this,
"MENU_LABEL_DELETE",
private val buttonsY = y + listHeight + gridGap
private val buttonSearch = UIItemTextButton(this,
"CONTEXT_WORLD_NEW",
hx - gridGap/2 - 2*deleteButtonWidth - gridGap,
buttonsY,
deleteButtonWidth,
readFromLang = true,
hasBorder = true,
alignment = UIItemTextButton.Companion.Alignment.CENTRE
).also {
it.clickOnceListener = { _,_ ->
full.requestTransition(1)
}
}
private val buttonTeleport = UIItemTextButton(this,
"GAME_ACTION_TELEPORT",
hx - gridGap/2 - deleteButtonWidth,
y + listHeight - buttonHeight,
buttonsY,
deleteButtonWidth,
readFromLang = true,
hasBorder = true,
alignment = UIItemTextButton.Companion.Alignment.CENTRE
)
private val buttonRenameWorld = UIItemTextButton(this,
).also {
it.clickOnceListener = { _,_ ->
if (selected?.worldInfo != null) {
full.host.teleportRequest = FixtureWorldPortal.TeleportRequest(
selected?.worldInfo?.diskSkimmer, null
)
full.setAsClose()
printdbg(this, "Teleport target set: ${full.host.teleportRequest}")
}
}
}
private val buttonRename = UIItemTextButton(this,
"MENU_LABEL_RENAME",
buttonDeleteWorld.posX - gridGap - deleteButtonWidth,
y + listHeight - buttonHeight,
hx + gridGap/2,
buttonsY,
deleteButtonWidth,
readFromLang = true,
hasBorder = true,
alignment = UIItemTextButton.Companion.Alignment.CENTRE
)
private val buttonDelete = UIItemTextButton(this,
"MENU_LABEL_DELETE",
hx + gridGap/2 + deleteButtonWidth + gridGap,
buttonsY,
deleteButtonWidth,
readFromLang = true,
hasBorder = true,
alignment = UIItemTextButton.Companion.Alignment.CENTRE
)
private val navRemoCon = UIItemListNavBarVertical(full, hx + 6 + UIItemWorldCellsSimple.width, y + 7, listHeight + 2, false)
private val worldList = ArrayList<WorldInfo>()
data class WorldInfo(
@@ -88,32 +135,54 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
}
}
private fun disableListEditButtons() {
buttonRename.isEnabled = false
buttonDelete.isEnabled = false
buttonTeleport.isEnabled = false
currentWorldSelected = false
}
private fun highlightListEditButtons(info: WorldInfo?) {
// will disable the delete and teleport button if the world is equal to the currently playing world
if (info == null) disableListEditButtons()
else {
buttonRename.isEnabled = true
buttonDelete.isEnabled = info.uuid != INGAME.world.worldIndex
buttonTeleport.isEnabled = info.uuid != INGAME.world.worldIndex
currentWorldSelected = info.uuid == INGAME.world.worldIndex
}
}
private var currentWorldSelected = false
init {
CommonResourcePool.addToLoadingList("terrarum-basegame-worldportalicons") {
TextureRegionPack(ModMgr.getGdxFile("basegame", "gui/worldportal_catbar.tga"), 30, 20)
}
CommonResourcePool.loadAll()
navRemoCon.scrollUpListener = { _,_ -> scrollItemPage(-1) }
navRemoCon.scrollDownListener = { _,_ -> scrollItemPage(1) }
addUIitem(buttonRenameWorld)
addUIitem(buttonDeleteWorld)
addUIitem(buttonDelete)
addUIitem(buttonRename)
addUIitem(buttonTeleport)
addUIitem(buttonSearch)
addUIitem(navRemoCon)
}
private var chunksUsed = 0
private val chunksMax = 100000
fun scrollItemPage(relativeAmount: Int) {
listPage = if (listPageCount == 0) 0 else (listPage + relativeAmount).fmod(listPageCount)
}
private lateinit var worldCells: Array<UIItemWorldCellsSimple>
private var selected: UIItemWorldCellsSimple? = null
private var selectedIndex: Int? = null
override fun show() {
private fun readWorldList() {
worldList.clear()
(INGAME.actorGamer.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(",").filter { it.isNotBlank() }.map {
it.ascii85toUUID().let { it to App.savegameWorlds[it] }
}.filter { it.second != null }.mapIndexed { index, (uuid, disk) ->
}.filter { it.second != null }.mapIndexed { index, (uuid, disk0) ->
val disk = disk0!!.loadable()
var chunksCount = 0
var seed = 0L
var lastPlayed = 0L
@@ -122,6 +191,8 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
var h = 0
var thumb: TextureRegion? = null
disk.rebuild()
JsonFetcher.readFromJsonString(ByteArray64Reader(disk!!.requestFile(-1)!!.contents.getContent() as ByteArray64, Common.CHARSET)).let {
JsonFetcher.forEachSiblings(it) { name, value ->
if (name == "width") w = value.asInt()
@@ -148,31 +219,63 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
}.let {
worldList.addAll(it)
}
chunksUsed = worldList.sumOf { it.dimensionInChunks }
listPageCount = ceil(worldList.size.toDouble() / listCount).toInt()
}
worldCells = Array(maxOf(worldList.size, listCount)) {
private var chunksUsed = 0
private val chunksMax = 100000
private lateinit var worldCells: Array<UIItemWorldCellsSimple>
private var selected: UIItemWorldCellsSimple? = null
private var selectedIndex: Int? = null
var listPage
set(value) {
navRemoCon.itemPage = if (listPageCount == 0) 0 else (value).fmod(listPageCount)
rebuildList()
}
get() = navRemoCon.itemPage
var listPageCount // TODO total size of current category / items.size
protected set(value) {
navRemoCon.itemPageCount = value
}
get() = navRemoCon.itemPageCount
private fun rebuildList() {
worldCells = Array(listCount) { it0 ->
val it = it0 + listCount * listPage
UIItemWorldCellsSimple(
this,
hx + gridGap / 2,
y + (gridGap + UIItemWorldCellsSimple.height) * it,
y + (gridGap + UIItemWorldCellsSimple.height) * it0,
worldList.getOrNull(it),
worldList.getOrNull(it)?.diskSkimmer?.getDiskName(Common.CHARSET)
).also { button ->
button.clickOnceListener = { _, _, _ ->
button.clickOnceListener = { _, _ ->
selected = button
selectedIndex = it
highlightListEditButtons(worldList.getOrNull(it))
updateUIbyButtonSelection()
}
}
}
}
override fun show() {
listPage = 0
readWorldList()
rebuildList()
uiItems.forEach { it.show() }
worldCells.forEach { it.show() }
selected = null
disableListEditButtons()
updateUIbyButtonSelection()
}
@@ -200,7 +303,11 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
override fun updateUI(delta: Float) {
uiItems.forEach { it.update(delta) }
worldCells.forEach { it.update(delta) }
if (::worldCells.isInitialized) worldCells.forEach { it.update(delta) }
if (currentWorldSelected) {
INGAME.setTooltipMessage(if (buttonTeleport.mouseUp || buttonDelete.mouseUp) Lang["CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING"] else null)
}
}
private val iconGap = 12f
@@ -231,7 +338,7 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons")
override fun renderUI(batch: SpriteBatch, camera: Camera) {
val memoryGaugeXpos = hx - memoryGaugeWidth - gridGap/2
val memoryGaugeYpos = y + listHeight - buttonHeight - gridGap - buttonHeight
val memoryGaugeYpos = y + listHeight - buttonHeight
val textXpos = memoryGaugeXpos + 3
// draw background //
@@ -291,7 +398,7 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
uiItems.forEach { it.render(batch, camera) }
worldCells.forEach { it.render(batch, camera) }
if (::worldCells.isInitialized) worldCells.forEach { it.render(batch, camera) }
// control hints
batch.color = Color.WHITE
@@ -300,15 +407,15 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
override fun hide() {
uiItems.forEach { it.hide() }
worldCells.forEach { it.hide() }
if (::worldCells.isInitialized) worldCells.forEach { it.hide() }
worldCells.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
if (::worldCells.isInitialized) worldCells.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
worldList.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
}
override fun dispose() {
uiItems.forEach { it.dispose() }
worldCells.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
if (::worldCells.isInitialized) worldCells.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
worldList.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
}
@@ -325,17 +432,50 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
if (this.isVisible) {
uiItems.forEach { it.touchUp(screenX, screenY, pointer, button) }
worldCells.forEach { it.touchUp(screenX, screenY, pointer, button) }
if (::worldCells.isInitialized) worldCells.forEach { it.touchUp(screenX, screenY, pointer, button) }
handler.subUIs.forEach { it.touchUp(screenX, screenY, pointer, button) }
return true
}
else return false
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
if (this.isVisible) {
uiItems.forEach { it.scrolled(amountX, amountY) }
if (::worldCells.isInitialized) worldCells.forEach { it.scrolled(amountX, amountY) }
handler.subUIs.forEach { it.scrolled(amountX, amountY) }
return true
}
else return false
}
override fun doOpening(delta: Float) {
super.doOpening(delta)
INGAME.pause()
INGAME.setTooltipMessage(null)
}
override fun doClosing(delta: Float) {
super.doClosing(delta)
INGAME.resume()
INGAME.setTooltipMessage(null)
}
override fun endOpening(delta: Float) {
super.endOpening(delta)
}
override fun endClosing(delta: Float) {
super.endClosing(delta)
UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required!
}
}
class UIItemWorldCellsSimple(
parent: UIWorldPortalListing,
val parent: UIWorldPortalListing,
initialX: Int,
initialY: Int,
internal val worldInfo: UIWorldPortalListing.WorldInfo? = null,
@@ -396,6 +536,15 @@ class UIItemWorldCellsSimple(
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
if (mouseUp) {
parent.scrollItemPage(amountY.toInt())
}
return true
}
override fun dispose() {
}
}

View File

@@ -1,28 +1,174 @@
package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.random.HQRNG
import net.torvald.random.XXHash64
import net.torvald.terrarum.App
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.WorldgenLoadScreen
import net.torvald.terrarum.modulebasegame.gameactors.FixtureWorldPortal
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.serialise.ReadActor
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.INVENTORY_CELLS_OFFSET_Y
import net.torvald.terrarum.savegame.ByteArray64Reader
import net.torvald.terrarum.savegame.VDFileID
import net.torvald.terrarum.savegame.VirtualDisk
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.ui.*
import net.torvald.terrarum.utils.RandomWordsName
/**
* Created by minjaesong on 2023-05-19.
*/
class UIWorldPortalSearch(val full: UIWorldPortal) : UICanvas() {
override var width: Int = Toolkit.drawWidth
override var height: Int = App.scr.height
// override var width: Int = Toolkit.drawWidth
// override var height: Int = App.scr.height
private val hugeTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/huge.png")))
private val largeTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/large.png")))
private val normalTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/normal.png")))
private val smallTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/small.png")))
private val tex = arrayOf(smallTex, normalTex, largeTex, hugeTex)
override var width = 480
override var height = 480
private val drawX = (Toolkit.drawWidth - width) / 2
private val drawY = (App.scr.height - height) / 2
private val radioCellWidth = 116
private val inputWidth = 340
private val radioX = (width - (radioCellWidth * tex.size + 9)) / 2
private val inputX = width - inputWidth
private val sizeSelY = 186 + 40
private val sizeSelector = UIItemInlineRadioButtons(this,
drawX + radioX, drawY + sizeSelY, radioCellWidth,
listOf(
{ Lang["CONTEXT_DESCRIPTION_TINY"] },
{ Lang["CONTEXT_DESCRIPTION_SMALL"] },
{ Lang["CONTEXT_DESCRIPTION_BIG"] },
{ Lang["CONTEXT_DESCRIPTION_HUGE"] }
)
)
private val rng = HQRNG()
private val nameInput = UIItemTextLineInput(this,
drawX + width - inputWidth, drawY + sizeSelY + 80, inputWidth,
{ RandomWordsName(4) }, InputLenCap(VirtualDisk.NAME_LENGTH, InputLenCap.CharLenUnit.UTF8_BYTES)
)
private val seedInput = UIItemTextLineInput(this,
drawX + width - inputWidth, drawY + sizeSelY + 120, inputWidth,
{ rng.nextLong().toString() }, InputLenCap(256, InputLenCap.CharLenUnit.CODEPOINTS)
)
private val goButtonWidth = 180
private val backButton = UIItemTextButton(this, "MENU_LABEL_BACK", drawX + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
private val goButton = UIItemTextButton(this, "MENU_LABEL_CONFIRM_BUTTON", drawX + width/2 + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, true, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
init {
goButton.clickOnceListener = { _, _ ->
val seed = try {
seedInput.getTextOrPlaceholder().toLong()
}
catch (e: NumberFormatException) {
XXHash64.hash(seedInput.getTextOrPlaceholder().toByteArray(Charsets.UTF_8), 10000)
}
val (wx, wy) = TerrarumIngame.WORLDPORTAL_NEW_WORLD_SIZE[sizeSelector.selection]
val worldParam = TerrarumIngame.NewWorldParameters(wx, wy, seed, nameInput.getTextOrPlaceholder())
full.host.teleportRequest = FixtureWorldPortal.TeleportRequest(null, worldParam)
full.setAsClose()
}
backButton.clickOnceListener = { _, _ ->
full.requestTransition(0)
}
addUIitem(sizeSelector)
addUIitem(seedInput) // order is important
addUIitem(nameInput) // because of the IME candidates overlay
addUIitem(goButton)
addUIitem(backButton)
}
override fun show() {
uiItems.forEach { it.show() }
seedInput.clearText()
seedInput.refreshPlaceholder()
nameInput.clearText()
nameInput.refreshPlaceholder()
}
private var oldPosX = full.posX
override fun updateUI(delta: Float) {
TODO("Not yet implemented")
uiItems.forEach { it.update(delta) }
}
override fun renderUI(batch: SpriteBatch, camera: Camera) {
TODO("Not yet implemented")
val posXDelta = posX - oldPosX
// ugh why won't you just scroll along??
seedInput.posX += posXDelta
nameInput.posX += posXDelta
goButton.posX += posXDelta
backButton.posX += posXDelta
batch.color = Color.WHITE
// ui title
val titlestr = Lang["CONTEXT_WORLD_NEW"]
App.fontUITitle.draw(batch, titlestr, drawX + (width - App.fontUITitle.getWidth(titlestr)).div(2).toFloat(), INVENTORY_CELLS_OFFSET_Y() - 72f)
// draw size previews
val texture = tex[sizeSelector.selection.coerceAtMost(tex.lastIndex)]
val tx = drawX + (width - texture.regionWidth) / 2
val ty = drawY + (160 - texture.regionHeight) / 2
batch.draw(texture, tx.toFloat(), ty.toFloat())
// border
batch.color = Toolkit.Theme.COL_INACTIVE
Toolkit.drawBoxBorder(batch, tx - 1, ty - 1, texture.regionWidth + 2, texture.regionHeight + 2)
batch.color = Color.WHITE
// size selector title
val sizestr = Lang["MENU_OPTIONS_SIZE"]
App.fontGame.draw(batch, sizestr, drawX + (width - App.fontGame.getWidth(sizestr)).div(2).toFloat(), drawY + sizeSelY - 40f)
// name/seed input labels
App.fontGame.draw(batch, Lang["MENU_NAME"], drawX, drawY + sizeSelY + 80)
App.fontGame.draw(batch, Lang["CONTEXT_GENERATOR_SEED"], drawX, drawY + sizeSelY + 120)
uiItems.forEach { it.render(batch, camera) }
oldPosX = posX
}
override fun hide() {
uiItems.forEach { it.hide() }
}
override fun dispose() {
TODO("Not yet implemented")
hugeTex.texture.dispose()
largeTex.texture.dispose()
normalTex.texture.dispose()
smallTex.texture.dispose()
}
}

View File

@@ -50,13 +50,13 @@ removefile:
fun checkFileSanity() {
if (!diskFile.exists()) throw NoSuchFileException(diskFile.absoluteFile)
if (diskFile.length() < 310L) throw RuntimeException("Invalid Virtual Disk file!")
if (diskFile.length() < 310L) throw RuntimeException("Invalid Virtual Disk file: ${diskFile.path}")
// check magic
val fis = FileInputStream(diskFile)
val magic = ByteArray(4).let { fis.read(it); it }
if (!magic.contentEquals(VirtualDisk.MAGIC)) throw RuntimeException("Invalid Virtual Disk file!")
if (!magic.contentEquals(VirtualDisk.MAGIC)) throw RuntimeException("Invalid Virtual Disk file: ${diskFile.path}")
fis.close()
}
@@ -221,7 +221,7 @@ removefile:
fa.read(4).toIntBig().toLong()
}
DiskEntry.SYMLINK -> 8L
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag for entryID $entryID at offset $offset") // FIXME no support for compressed file
}
@@ -251,7 +251,7 @@ removefile:
EntrySymlink(target)
}
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag for entryID $entryID at offset $offset") // FIXME no support for compressed file
}
return DiskEntry(entryID, parent, creationTime, modifyTime, entryContent)

View File

@@ -503,8 +503,8 @@ object VDUtil {
* Throws an exception if specified size cannot fit into the disk
*/
fun VirtualDisk.checkCapacity(newSize: Long) {
if (this.usedBytes + newSize > this.capacity)
throw IOException("Not enough space on the disk")
// if (this.usedBytes + newSize > this.capacity)
// throw IOException("Not enough space on the disk")
}
fun ByteArray64.toIntBig(): Int {
if (this.size != 4L)

View File

@@ -72,7 +72,7 @@ Version 254 is a customised version of TEVD tailored to be used as a savegame fo
Int8 Disk properties flag 1
0th bit: readonly
Int8 Save type (0b 0000 00ab)
b: unset - full save; set - quick save
b: unset - full save; set - quicksave (only applicable to worlds -- quicksave just means the disk is in dirty state)
a: set - generated by autosave
Int8 Kind of the Save file
0: Undefined (or very old version of the game)
@@ -145,9 +145,7 @@ class VirtualDisk(
var extraInfoBytes = ByteArray(16)
val entries = HashMap<EntryID, DiskEntry>()
var isReadOnly: Boolean
set(value) { extraInfoBytes[0] = (extraInfoBytes[0] and 0xFE.toByte()) or value.toBit() }
get() = capacity == 0L || (extraInfoBytes.size > 0 && extraInfoBytes[0].and(1) == 1.toByte())
val isReadOnly = false
var saveMode: Int
set(value) { extraInfoBytes[1] = value.toByte() }
get() = extraInfoBytes[1].toUint()
@@ -260,6 +258,7 @@ object VDFileID {
const val SPRITEDEF = -2L
const val SPRITEDEF_GLOW = -3L
const val LOADORDER = -4L
const val PLAYER_SCREENSHOT = -5L
const val BODYPART_TO_ENTRY_MAP = -1025L
const val BODYPARTGLOW_TO_ENTRY_MAP = -1026L
}
@@ -279,6 +278,11 @@ fun diskIDtoReadableFilename(id: EntryID, saveKind: Int?): String = when (id) {
"spritedef-glow"
else
"file #$id"
VDFileID.PLAYER_SCREENSHOT ->
if (saveKind == PLAYER_DATA)
"screenshot.tga.gz"
else
"file #$id"
VDFileID.LOADORDER -> "loadOrder.txt"
// -16L -> "blockcodex.json.gz"
// -17L -> "itemcodex.json.gz"

View File

@@ -415,41 +415,6 @@ class VirtualDiskCracker(val sysCharset: Charset = Charsets.UTF_8) : JFrame() {
}
}
})
menuEdit.add("Resize Disk…").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
val dialog = OptionSize()
val confirmed = dialog.showDialog("Input") == JOptionPane.OK_OPTION
if (confirmed) {
vdisk!!.capacity = (dialog.capacity.value as Long).toLong()
updateDiskInfo()
setStat("Disk resized")
}
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuEdit.addSeparator()
menuEdit.add("Set/Unset Write Protection").addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
if (vdisk != null) {
try {
vdisk!!.isReadOnly = vdisk!!.isReadOnly.not()
updateDiskInfo()
setStat("Disk write protection ${if (vdisk!!.isReadOnly) "" else "dis"}engaged")
}
catch (e: Exception) {
e.printStackTrace()
popupError(e.toString())
}
}
}
})
menuBar.add(menuEdit)
val menuManage = JMenu("Manage")
@@ -638,8 +603,7 @@ class VirtualDiskCracker(val sysCharset: Charset = Charsets.UTF_8) : JFrame() {
}
private fun getDiskInfoText(disk: VirtualDisk): String {
return """Name: ${String(disk.diskName, sysCharset)}
Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free)
Write protected: ${disk.isReadOnly.toEnglish()}"""
Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free)"""
}

View File

@@ -111,7 +111,7 @@ class DummyTogglePane : UICanvas() {
private var timer = 0f
init {
button1.clickOnceListener = { _,_,_ ->
button1.clickOnceListener = { _,_ ->
button1.toggle()
}
uiItems.add(button1)

View File

@@ -377,7 +377,7 @@ class BasicDebugInfoWindow : UICanvas() {
blendNormalStraightAlpha(batch)
batch.end()
/*batch.end()
gdxBlendNormalStraightAlpha()
Terrarum.inShapeRenderer {
it.color = uiColour
@@ -388,7 +388,7 @@ class BasicDebugInfoWindow : UICanvas() {
it.line(uiX + halfW, App.scr.height - (uiY + halfH), uiX + halfW + pointDX, App.scr.height - (uiY + halfH + pointDY))
it.color = Color.GRAY
}
batch.begin()
batch.begin()*/
App.fontSmallNumbers.draw(batch, gamepad.getName(), Toolkit.drawWidth - (gamepad.getName().length + 2f) * TinyAlphNum.W, uiY.toFloat() + h + 2)

View File

@@ -64,7 +64,7 @@ class ConsoleWindow : UICanvas() {
init {
reset()
addUIitem(textinput)
textinput.isActive = false
textinput.isEnabled = false
}
private val lb = ArrayList<String>()
@@ -99,7 +99,7 @@ class ConsoleWindow : UICanvas() {
clickLatched = false
}
textinput.isActive = (isOpened && !isClosing)
textinput.isEnabled = (isOpened && !isClosing)
}
override fun renderUI(batch: SpriteBatch, camera: Camera) {
@@ -269,14 +269,14 @@ class ConsoleWindow : UICanvas() {
drawOffY = MovementInterpolator.fastPullOut(openingTimeCounter.toFloat() / openCloseTime.toFloat(),
0f, -height.toFloat()
)*/
textinput.isActive = false
textinput.isEnabled = false
textinput.mouseoverUpdateLatch = false
}
override fun endOpening(delta: Float) {
drawOffY = 0f
openingTimeCounter = 0f
textinput.isActive = true
textinput.isEnabled = true
textinput.mouseoverUpdateLatch = true
}

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.ui
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.graphics.glutils.FloatFrameBuffer
@@ -9,6 +10,7 @@ import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.roundToInt
@@ -28,6 +30,8 @@ object Toolkit : Disposable {
val COL_SELECTED = Color(0x00f8ff_ff) // cyan, HIGHLY SATURATED
val COL_MOUSE_UP = Color(0xfff066_ff.toInt()) // yellow (all yellows are of low saturation according to the colour science)
val COL_DISABLED = Color(0xaaaaaaff.toInt())
val COL_RED = Color(0xff8888ff.toInt())
val COL_REDD = Color(0xff4448ff.toInt())
/*
Try this for alt colour set:
@@ -93,6 +97,10 @@ object Toolkit : Disposable {
get() = App.scr.width - if (App.getConfigBoolean("fx_streamerslayout")) App.scr.chatWidth else 0
val drawWidthf: Float
get() = drawWidth.toFloat()
val hdrawWidth: Int
get() = drawWidth / 2
val hdrawWidthf: Float
get() = hdrawWidth.toFloat()
fun drawCentered(batch: SpriteBatch, image: Texture, screenPosY: Int, ui: UICanvas? = null) {
val imageW = image.width
@@ -114,6 +122,11 @@ object Toolkit : Disposable {
batch.draw(image, targetW.minus(imageW).ushr(1).toFloat() + offsetX, screenPosY.toFloat() + offsetY)
}
fun drawTextCentered(batch: SpriteBatch, font: TerrarumSansBitmap, text: String, tbw: Int, tbx: Int, tby: Int) {
val tw = font.getWidth(text)
font.draw(batch, text, tbx + (tbw - tw) / 2, tby)
}
fun fillArea(batch: SpriteBatch, x: Int, y: Int, w: Int, h: Int) {
batch.draw(textureWhiteSquare, x.toFloat(), y.toFloat(), w.toFloat(), h.toFloat())
}
@@ -250,7 +263,7 @@ object Toolkit : Disposable {
(batch as FlippingSpriteBatch).drawFlipped(fboBlur.colorBufferTexture, x.toFloat(), y.toFloat())
}
fun drawBaloon(batch: SpriteBatch, x: Float, y: Float, w: Float, h: Float) {
fun drawBaloon(batch: SpriteBatch, x: Float, y: Float, w: Float, h: Float, opacity: Float = 1f) {
// centre area
/*batch.draw(baloonTile.get(1, 1), x, y, w, h)
@@ -267,9 +280,9 @@ object Toolkit : Disposable {
batch.draw(baloonTile.get(0, 2), x - baloonTile.tileW, y + h)*/
batch.color = Theme.COL_CELL_FILL_OPAQUE
batch.color = Theme.COL_CELL_FILL_OPAQUE.cpy().mul(1f,1f,1f,opacity)
fillArea(batch, x - 4, y - 4, w + 8, h + 8)
batch.color = Theme.COL_INACTIVE
batch.color = Theme.COL_INACTIVE.cpy().mul(1f,1f,1f,opacity)
drawBoxBorder(batch, x - 4, y - 4, w + 8, h + 8)
}

View File

@@ -32,7 +32,7 @@ class UIAutosaveNotifier : UICanvas() {
private var errored = false
private var normalCol = Color.WHITE
private var errorCol = Color(0xFF8888FF.toInt())
private var errorCol = Toolkit.Theme.COL_RED
override fun updateUI(delta: Float) {
spinnerTimer += delta

View File

@@ -128,9 +128,15 @@ abstract class UICanvas(
/** A function that is run ONCE when the UI is requested to be opened; will work identical to [endOpening] if [openCloseTime] is zero */
open fun show() {}
open fun show() {
uiItems.forEach { it.show() }
handler.subUIs.forEach { it.show() }
}
/** A function that is run ONCE when the UI is requested to be closed; will work identical to [endClosing] if [openCloseTime] is zero */
open fun hide() {}
open fun hide() {
uiItems.forEach { it.hide() }
handler.subUIs.forEach { it.hide() }
}
/** **DO NOT CALL THIS FUNCTION FOR THE ACTUAL UPDATING OF THE UI — USE update() INSTEAD**
@@ -184,7 +190,7 @@ abstract class UICanvas(
if (!uiItems.contains(uiItem)) uiItems.add(uiItem)
}
fun mouseInScreen(x: Int, y: Int) = x in 0 until App.scr.width && y in 0 until App.scr.height
fun mouseInScreen(x: Int, y: Int) = x in 0 until App.scr.windowW && y in 0 until App.scr.windowH
/**
* Called by the screen's InputProcessor

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Disposable
import net.torvald.terrarum.App
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
@@ -95,31 +96,33 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
protected var mouseLatched = Terrarum.mouseDown
/** UI to call (show up) while mouse is up */
open val mouseOverCall: UICanvas? = null
open var mouseOverCall: UICanvas? = null
// kind of listener implementation
/** Fired once for every update
* Parameter: delta */
open var updateListener: ((Float) -> Unit)? = null
open var updateListener: ((Float) -> Unit) = {}
/** Parameter: keycode */
open var keyDownListener: ((Int) -> Unit)? = null
open var keyDownListener: ((Int) -> Unit) = {}
/** Parameter: keycode */
open var keyUpListener: ((Int) -> Unit)? = null
open var keyTypedListener: ((Char) -> Unit)? = null
open var touchDraggedListener: ((Int, Int, Int) -> Unit)? = null
open var keyUpListener: ((Int) -> Unit) = {}
open var keyTypedListener: ((Char) -> Unit) = {}
open var touchDraggedListener: ((Int, Int, Int) -> Unit) = { _,_,_ -> }
/** Parameters: screenX, screenY, pointer, button */
open var touchDownListener: ((Int, Int, Int, Int) -> Unit)? = null
open var touchUpListener: ((Int, Int, Int, Int) -> Unit)? = null
@Deprecated("Not really deprecated but you MOST DEFINITELY want to use clickOnceListener(mouseX, mouseY) instead.")
open var touchDownListener: ((Int, Int, Int, Int) -> Unit) = { _,_,_,_ -> }
open var touchUpListener: ((Int, Int, Int, Int) -> Unit) = { _,_,_,_ -> }
/** Parameters: amountX, amountY */
open var scrolledListener: ((Float, Float) -> Unit)? = null
/** Parameters: relative mouseX, relative mouseY, button
*
open var scrolledListener: ((Float, Float) -> Unit) = { _,_ -> }
/** Parameters: relative mouseX, relative mouseY
* ClickOnce listeners are only fired when clicked with primary mouse button
* PROTIP: if clickOnceListener does not seem to work, make sure your parent UI is handling touchDown() and touchUp() events!
*/
open var clickOnceListener: ((Int, Int, Int) -> Unit)? = null
open var clickOnceListener: ((Int, Int) -> Unit) = { _,_ -> }
open var clickOnceListenerFired = false
/** Parameters: relative mouseX, mouseY */
open var mouseUpListener: ((Int, Int) -> Unit) = { _,_, -> }
/** Since gamepads can't just choose which UIItem to control, this variable is used to allow processing of
* gamepad button events for one or more UIItems in one or more UICanvases. */
@@ -128,6 +131,11 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
/**
* Whether the button is "available" or not to the player
*/
open var isEnabled = true
/**
* Whether the button should receive updates
*/
open var isActive = true
@@ -137,28 +145,27 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
open fun update(delta: Float) {
if (parentUI.isVisible) {
if (updateListener != null) {
updateListener!!.invoke(delta)
}
if (isActive) {
updateListener.invoke(delta)
mouseOverCall?.update(delta)
if (mouseUp) {
if (mouseOverCall?.isVisible ?: false) {
if (mouseOverCall?.isVisible == true) {
mouseOverCall?.setAsOpen()
}
mouseOverCall?.updateUI(delta)
mouseUpListener.invoke(itemRelativeMouseX, itemRelativeMouseY)
}
else {
if (mouseOverCall?.isVisible ?: false) {
if (mouseOverCall?.isVisible == true) {
mouseOverCall?.setAsClose()
}
}
}
if (!mouseUp) mouseLatched = false
if (!mouseUp) mouseLatched = false
}
}
}
@@ -167,36 +174,36 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
*/
open fun render(batch: SpriteBatch, camera: Camera) {
if (parentUI.isVisible) {
if (isActive) {
// if (isActive) {
mouseOverCall?.render(batch, camera)
if (mouseUp) {
mouseOverCall?.renderUI(batch, camera)
}
}
// }
}
}
// keyboard controlled
open fun keyDown(keycode: Int): Boolean {
if (parentUI.isVisible && keyDownListener != null && isActive) {
keyDownListener!!.invoke(keycode)
if (parentUI.isVisible && isEnabled) {
keyDownListener.invoke(keycode)
return true
}
return false
}
open fun keyUp(keycode: Int): Boolean {
if (parentUI.isVisible && keyUpListener != null && isActive) {
keyUpListener!!.invoke(keycode)
if (parentUI.isVisible && isEnabled) {
keyUpListener.invoke(keycode)
return true
}
return false
}
open fun keyTyped(character: Char): Boolean {
if (parentUI.isVisible && keyTypedListener != null && isActive) {
keyTypedListener!!.invoke(character)
if (parentUI.isVisible && isEnabled) {
keyTypedListener.invoke(character)
return true
}
@@ -205,8 +212,8 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
// mouse controlled
open fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean {
if (parentUI.isVisible && touchDraggedListener != null && isActive) {
touchDraggedListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer)
if (parentUI.isVisible && isEnabled) {
touchDraggedListener.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer)
return true
}
@@ -215,14 +222,14 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
open fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
var actionDone = false
if (parentUI.isVisible && isActive) {
if (touchDownListener != null && mouseUp) {
touchDownListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button)
if (parentUI.isVisible && isEnabled) {
if (mouseUp) {
touchDownListener.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button)
actionDone = true
}
if (clickOnceListener != null && !clickOnceListenerFired && mouseUp) {
clickOnceListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, button)
if (!clickOnceListenerFired && mouseUp && button == App.getConfigInt("config_mouseprimary")) {
clickOnceListener.invoke(itemRelativeMouseX, itemRelativeMouseY)
actionDone = true
}
}
@@ -232,16 +239,16 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
open fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
clickOnceListenerFired = false
if (parentUI.isVisible && touchUpListener != null && mouseUp) {
touchUpListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button)
if (parentUI.isVisible && mouseUp) {
touchUpListener.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button)
return true
}
return false
}
open fun scrolled(amountX: Float, amountY: Float): Boolean {
if (parentUI.isVisible && scrolledListener != null && mouseUp && isActive) {
scrolledListener!!.invoke(amountX, amountY)
if (parentUI.isVisible && mouseUp && isEnabled) {
scrolledListener.invoke(amountX, amountY)
return true
}

View File

@@ -18,7 +18,7 @@ class UIItemHorizontalFadeSlide(
//transitionLength: Float,
currentPosition: Float,
vararg uis: UICanvas
) : UIItemTransitionContainer(parent, initialX, initialY, width, height, 0.10f, currentPosition, uis) {
) : UIItemTransitionContainer(parent, initialX, initialY, width, height, 0.12f, currentPosition, uis) {
fun getOffX(index: Int) = ((currentPosition - index) * width / 2f).roundToInt()
fun getOpacity(index: Int) = 1f - (currentPosition - index).absoluteValue.coerceIn(0f, 1f)

View File

@@ -5,6 +5,7 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.BlendMode
import net.torvald.terrarum.abs
import net.torvald.terrarum.blendNormalStraightAlpha
/**
@@ -33,14 +34,24 @@ open class UIItemImageButton(
initialX: Int,
initialY: Int,
/** this does NOT resize the image; use imageDrawWidth to actually resize the image */
override val width: Int = image.regionWidth,
/** this does NOT resize the image; use imageDrawHeight to actually resize the image */
override val height: Int = image.regionHeight,
/** When clicked, toggle its "lit" status */
var highlightable: Boolean
var highlightable: Boolean,
/** Changes the appearance to use a border instead of colour-changing image for highlighter */
val useBorder: Boolean = false,
/** Image won't be place at right position if `image.regionWidth != imageDrawWidth`; define the `width` argument to avoid the issue */
val imageDrawWidth: Int = image.regionWidth,
/** Image won't be place at right position if `image.regionHeight != imageDrawHeight`; define the `height` argument to avoid the issue */
val imageDrawHeight: Int = image.regionHeight,
) : UIItem(parent, initialX, initialY) {
var highlighted = false
var extraDrawOp: (UIItem, SpriteBatch) -> Unit = { _,_ -> }
override fun render(batch: SpriteBatch, camera: Camera) {
// draw background
@@ -60,15 +71,22 @@ open class UIItemImageButton(
Toolkit.fillArea(batch, posX.toFloat(), posY.toFloat(), width.toFloat(), height.toFloat())
}
blendNormalStraightAlpha(batch)
// draw image
blendNormalStraightAlpha(batch)
batch.color = if (highlighted) highlightCol
else if (mouseUp) activeCol
else if (useBorder) Toolkit.Theme.COL_INACTIVE else inactiveCol
if (useBorder) {
Toolkit.drawBoxBorder(batch, posX - 1f, posY - 1f, width + 2f, height + 2f)
batch.color = Color.WHITE
}
batch.draw(image, (posX + (width - imageDrawWidth) / 2).toFloat(), (posY + (height - imageDrawHeight) / 2).toFloat(), imageDrawWidth.toFloat(), imageDrawHeight.toFloat())
batch.color = if (highlighted) highlightCol
else if (mouseUp) activeCol
else inactiveCol
batch.draw(image, (posX + (width - image.regionWidth) / 2).toFloat(), (posY + (height - image.regionHeight) / 2).toFloat())
extraDrawOp(this, batch)
}
override fun dispose() {

View File

@@ -85,45 +85,6 @@ class UIItemIntSlider(
}
// TODO unimplemented
override val mouseUp: Boolean
get() = super.mouseUp
override val mousePushed: Boolean
get() = super.mousePushed
override val mouseOverCall: UICanvas?
get() = super.mouseOverCall
override var updateListener: ((Float) -> Unit)?
get() = super.updateListener
set(_) {}
override var keyDownListener: ((Int) -> Unit)?
get() = super.keyDownListener
set(_) {}
override var keyUpListener: ((Int) -> Unit)?
get() = super.keyUpListener
set(_) {}
override var touchDraggedListener: ((Int, Int, Int) -> Unit)?
get() = super.touchDraggedListener
set(_) {}
override var touchDownListener: ((Int, Int, Int, Int) -> Unit)?
get() = super.touchDownListener
set(_) {}
override var touchUpListener: ((Int, Int, Int, Int) -> Unit)?
get() = super.touchUpListener
set(_) {}
override var scrolledListener: ((Float, Float) -> Unit)?
get() = super.scrolledListener
set(_) {}
override var clickOnceListener: ((Int, Int, Int) -> Unit)?
get() = super.clickOnceListener
set(_) {}
override var clickOnceListenerFired: Boolean
get() = super.clickOnceListenerFired
set(_) {}
override var controllerInFocus: Boolean
get() = super.controllerInFocus
set(_) {}
override fun dispose() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.

Some files were not shown because too many files have changed in this diff Show More