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/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/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="$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/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/prtree.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/Terrarum_Joise.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-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-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="$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="$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="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.21/kotlin-stdlib-1.8.21.jar" path-in-jar="/" />
<element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-reflect.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="$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.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="$KOTLIN_BUNDLED$/lib/kotlin-stdlib.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/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="$PROJECT_DIR$/lib/js-scriptengine-22.3.1.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/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> </root>
</artifact> </artifact>
</component> </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) { override fun doOpening(delta: Float) {
super.doOpening(delta)
fixture.startVM() fixture.startVM()
} }
override fun doClosing(delta: Float) {
}
override fun endOpening(delta: Float) {
}
override fun endClosing(delta: Float) {
}
override fun dispose() { override fun dispose() {
fbo.dispose() fbo.dispose()

Binary file not shown.

View File

@@ -7,12 +7,10 @@
"MENU_LABEL_PRESS_START_SYMBOL": "Press >", "MENU_LABEL_PRESS_START_SYMBOL": "Press >",
"MENU_MODULES" : "Modules", "MENU_MODULES" : "Modules",
"MENU_CREDIT_GPL_DNT" : "GPL", "MENU_CREDIT_GPL_DNT" : "GPL",
"MENU_LABEL_JVM_DNT" : "JVM",
"GAME_ACTION_MOVE_VERB" : "Move", "GAME_ACTION_MOVE_VERB" : "Move",
"GAME_ACTION_ZOOM" : "Zoom", "GAME_ACTION_ZOOM" : "Zoom",
"MENU_LABEL_RESET" : "Reset", "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_OPTION_STREAMERS_LAYOUT": "Chat Overlay",
"MENU_LABEL_RESTART_REQUIRED": "Restart Required", "MENU_LABEL_RESTART_REQUIRED": "Restart Required",
"MENU_LABEL_KEYBOARD_LAYOUT": "Keyboard Layout", "MENU_LABEL_KEYBOARD_LAYOUT": "Keyboard Layout",
@@ -21,11 +19,19 @@
"MENU_OPTIONS_BLUR": "Blur", "MENU_OPTIONS_BLUR": "Blur",
"MENU_OPTIONS_PARTICLES": "Particles", "MENU_OPTIONS_PARTICLES": "Particles",
"MENU_IO_IMPORT": "Import", "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_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_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", "n": "MENU_OPTIONS",
"s": "Stillingar " "s": "Valkostir "
}, },
{ {
"n": "MENU_OPTIONS_ADVANCEDGRAPHICS", "n": "MENU_OPTIONS_ADVANCEDGRAPHICS",
@@ -2395,11 +2395,11 @@
}, },
{ {
"n": "MENU_OPTIONS_GAMEPLAY", "n": "MENU_OPTIONS_GAMEPLAY",
"s": "Gameplay Options" "s": "Leikvalkostir"
}, },
{ {
"n": "MENU_OPTIONS_GRAPHICS", "n": "MENU_OPTIONS_GRAPHICS",
"s": "Grafíkstillingar" "s": "Grafíkvalkostir"
}, },
{ {
"n": "MENU_OPTIONS_HUD", "n": "MENU_OPTIONS_HUD",

View File

@@ -15,5 +15,15 @@
"MENU_OPTIONS_DITHER": "Dither", "MENU_OPTIONS_DITHER": "Dither",
"MENU_OPTIONS_BLUR": "Óskýrt", "MENU_OPTIONS_BLUR": "Óskýrt",
"MENU_OPTIONS_PARTICLES": "Eind", "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": "경고—건강과 안전을 위하여", "APP_WARNING_HEALTH_AND_SAFETY": "경고—건강과 안전을 위하여",
"MENU_LABEL_PRESS_START_SYMBOL": ">을 누르세요", "MENU_LABEL_PRESS_START_SYMBOL": ">을 누르세요",
"MENU_MODULES" : "모듈", "MENU_MODULES" : "모듈",
"GAME_ACTION_MOVE_VERB" : "이동하기", "GAME_ACTION_MOVE_VERB" : "이동하기",
"GAME_ACTION_ZOOM" : "확대·축소", "GAME_ACTION_ZOOM" : "확대·축소",
"MENU_LABEL_RESET" : "재설정", "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_OPTION_STREAMERS_LAYOUT": "채팅창 오버레이",
"MENU_LABEL_RESTART_REQUIRED": "재시작 필요", "MENU_LABEL_RESTART_REQUIRED": "재시작 필요",
"MENU_LABEL_KEYBOARD_LAYOUT": "자판 배열", "MENU_LABEL_KEYBOARD_LAYOUT": "자판 배열",
@@ -20,10 +19,19 @@
"MENU_OPTIONS_BLUR": "흐림", "MENU_OPTIONS_BLUR": "흐림",
"MENU_OPTIONS_PARTICLES": "입자 수", "MENU_OPTIONS_PARTICLES": "입자 수",
"MENU_IO_IMPORT": "가져오기", "MENU_IO_IMPORT": "가져오기",
"APP_NOMODULE_1": "현재 불러와진 모듈이 없습니다.",
"APP_NOMODULE_2": "다음의 파일에서 불러오기 순서를 설정하고 게임을 재시작하십시오.",
"MENU_LABEL_KEYCONFIG_HELP1": "키캡을 클릭해 컨트롤을 배정하십시오",
"MENU_LABEL_IME_TOGGLE": "입력기 켜고 끄기", "MENU_LABEL_IME_TOGGLE": "입력기 켜고 끄기",
"MENU_LABEL_PASTE_FROM_CLIPBOARD": "복사한 텍스트 붙여넣기", "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_ACTION_CRAFT": "Craft",
"GAME_CRAFTING": "Crafting", "GAME_CRAFTING": "Crafting",
"GAME_CRAFTABLE_ITEMS": "Craftable Items", "GAME_CRAFTABLE_ITEMS": "Craftable Items",
"CONTEXT_WORLD_SEARCH": "World Search", "MENU_LABEL_RENAME": "Rename",
"CONTEXT_WORLD_LIST": "Worlds List", "GAME_ACTION_TELEPORT": "Teleport",
"MENU_LABEL_RENAME": "Rename" "CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "This is a world currently playing."
} }

View File

@@ -17,5 +17,8 @@
"GAME_ACTION_QUICKSEL": "빠른 선택", "GAME_ACTION_QUICKSEL": "빠른 선택",
"GAME_ACTION_CRAFT": "제작하기", "GAME_ACTION_CRAFT": "제작하기",
"GAME_CRAFTING": "제작", "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 chmod +x $DESTDIR/AppRun
# Copy over a Java runtime # 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 # Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/ cp -r "../assets_release" $DESTDIR/
mv $DESTDIR/assets_release $DESTDIR/assets mv $DESTDIR/assets_release $DESTDIR/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/assets/ cp "../out/TerrarumBuild.jar" $DESTDIR/out/
# Pack everything to AppImage # Pack everything to AppImage
ARCH=arm_aarch64 "./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; } 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 chmod +x $DESTDIR/AppRun
# Copy over a Java runtime # 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 # Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/ cp -r "../assets_release" $DESTDIR/
mv $DESTDIR/assets_release $DESTDIR/assets mv $DESTDIR/assets_release $DESTDIR/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/assets/ cp "../out/TerrarumBuild.jar" $DESTDIR/out/
# Pack everything to AppImage # Pack everything to AppImage
"./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; } "./$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 chmod +x $DESTDIR/Contents/MacOS/Terrarum.sh
# Copy over a Java runtime # 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 # Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/Contents/MacOS/ cp -r "../assets_release" $DESTDIR/Contents/MacOS/
mv $DESTDIR/Contents/MacOS/assets_release $DESTDIR/Contents/MacOS/assets 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" echo "Build successful: $DESTDIR"

View File

@@ -25,11 +25,12 @@ cp $SRCFILES/Terrarum.sh $DESTDIR/Contents/MacOS/
chmod +x $DESTDIR/Contents/MacOS/Terrarum.sh chmod +x $DESTDIR/Contents/MacOS/Terrarum.sh
# Copy over a Java runtime # 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 # Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/Contents/MacOS/ cp -r "../assets_release" $DESTDIR/Contents/MacOS/
mv $DESTDIR/Contents/MacOS/assets_release $DESTDIR/Contents/MacOS/assets 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" echo "Build successful: $DESTDIR"

View File

@@ -16,15 +16,21 @@ rm -rf $DESTDIR || true
mkdir $DESTDIR mkdir $DESTDIR
# Prepare an application # 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 # 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 # Copy over all the assets and a jarfile
cp -r "../assets_release" $DESTDIR/ cp -r "../assets_release" $DESTDIR/
mv $DESTDIR/assets_release $DESTDIR/assets mv $DESTDIR/assets_release $DESTDIR/assets
cp -r "../out/TerrarumBuild.jar" $DESTDIR/assets/ cp "../out/TerrarumBuild.jar" $DESTDIR/out/
# Temporary solution: zip everything # Temporary solution: zip everything
zip -r -9 -l "out/TerrarumWindows.x86.zip" $DESTDIR zip -r -9 -l "out/TerrarumWindows.x86.zip" $DESTDIR

View File

@@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
cd "${0%/*}" 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 #!/bin/bash
cd "${0%/*}" 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 #!/bin/bash
cd "${0%/*}" 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 #!/bin/bash
cd "${0%/*}" 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 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); 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. /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
* here is the interpolation matrix * here is the interpolation matrix
* m = [ 0.0 1.0 0.0 0.0 ] * 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) { public static float interpolateHermite(float scale, float p0, float p1, float p2, float p3) {
return interpolateHermite(scale, p0, p1, p2, p3, 1f, 0f); // return interpolateHermite(scale, p0, p1, p2, p3, 0f, 0f);
}
public static float interpolateHermite(float scale, float p0, float p1, float p2, float p3, float tension, float bias) {
float mu2 = scale * scale; float mu2 = scale * scale;
float mu3 = mu2 * scale; float mu3 = mu2 * scale;
float biasTensionTerms = 0.5f;//(1f + bias) * (1f - tension) / 2f;
float m0 = (p1 - p0) * (1f + bias) * (1f - tension) / 2f; float m0 = (p1 - p0) * biasTensionTerms;
m0 += (p2 - p1) * (1f + bias) * (1f - tension) / 2f; float mTemp = (p2 - p1) * biasTensionTerms;
float m1 = (p2 - p1) * (1f + bias) * (1f - tension) / 2f; m0 += mTemp;
m1 += (p3 - p2) * (1f + bias) * (1f - tension) / 2f; float m1 = mTemp;
m1 += (p3 - p2) * biasTensionTerms;
float a0 = 2 * mu3 - 3 * mu2 + 1; float a0 = 2*mu3 - 3*mu2 + 1;
float a1 = mu3 - 2 * mu2 + scale; float a1 = 1*mu3 - 2*mu2 + scale;
float a2 = mu3 - mu2; float a2 = 1*mu3 - 1*mu2 + 0;
float a3 = -2 * mu3 + 3 * mu2; 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) { public static int hashGeoCoord(int x, int y) {
int p = ((x & 65535) << 16) | (y & 65535); 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) { 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. * 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 { return this.javaClass.getDeclaredField(name).let {
it.isAccessible = true 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 { return this.javaClass.getDeclaredMethod(name, *(params.map { it.javaClass }.toTypedArray())).let {
it.isAccessible = true 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; 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) * 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) * Sorted by the lastplaytime, in reverse order (index 0 is the most recent game played)
*/ */
public static ArrayList<UUID> sortedSavegameWorlds = new ArrayList(); 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 HashMap<UUID, String> savegameWorldsName = new HashMap<>();
public static ArrayList<UUID> sortedPlayers = new ArrayList(); 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 HashMap<UUID, String> savegamePlayersName = new HashMap<>();
public static void updateListOfSavegames() { public static void updateListOfSavegames() {
@@ -346,10 +352,13 @@ public class App implements ApplicationListener {
processorVendor = "Unknown CPU"; processorVendor = "Unknown CPU";
} }
if (processor.startsWith("Apple M") && Objects.equals(systemArch, "aarch64")) { if (processor.startsWith("Apple M") && systemArch.equals("aarch64")) {
isAppleM = true; isAppleM = true;
System.out.println("Apple Proprietary "+processor+" detected; don't expect smooth sailing..."); 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) { if (!IS_DEVELOPMENT_BUILD) {
var p = UnsafeHelper.INSTANCE.allocate(64); var p = UnsafeHelper.INSTANCE.allocate(64);
@@ -374,8 +383,8 @@ public class App implements ApplicationListener {
ShaderProgram.pedantic = false; ShaderProgram.pedantic = false;
scr = new TerrarumScreenSize(getConfigInt("screenwidth"), getConfigInt("screenheight")); scr = new TerrarumScreenSize(getConfigInt("screenwidth"), getConfigInt("screenheight"));
int width = (int) Math.round(scr.getWidth() * scr.getMagn()); int width = scr.getWindowW();
int height = (int) Math.round(scr.getHeight() * scr.getMagn()); int height = scr.getWindowH();
Lwjgl3ApplicationConfiguration appConfig = new Lwjgl3ApplicationConfiguration(); Lwjgl3ApplicationConfiguration appConfig = new Lwjgl3ApplicationConfiguration();
//appConfig.useGL30 = false; // https://stackoverflow.com/questions/46753218/libgdx-should-i-use-gl30 //appConfig.useGL30 = false; // https://stackoverflow.com/questions/46753218/libgdx-should-i-use-gl30
@@ -557,7 +566,7 @@ public class App implements ApplicationListener {
false, false,
64, false, 0.5f, false 64, false, 0.5f, false
); );
fontUITitle.setInterchar(2); fontUITitle.setInterchar(1);
fontGameFBO = new TerrarumSansBitmap(FONT_DIR, false, true, false, fontGameFBO = new TerrarumSansBitmap(FONT_DIR, false, true, false,
false, false,
64, false, 203f/255f, false 64, false, 203f/255f, false
@@ -631,7 +640,6 @@ public class App implements ApplicationListener {
// process screenshot request // process screenshot request
if (screenshotRequested) { if (screenshotRequested) {
FrameBufferManager.begin(postProcessorOutFBO); FrameBufferManager.begin(postProcessorOutFBO);
screenshotRequested = false;
try { try {
Pixmap p = Pixmap.createFromFrameBuffer(0, 0, scr.getWidth(), scr.getHeight()); 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); 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()); Terrarum.INSTANCE.getIngame().sendNotification("Failed to take screenshot: "+e.getMessage());
} }
FrameBufferManager.end(); FrameBufferManager.end();
screenshotRequested = false;
} }
@@ -767,12 +776,9 @@ public class App implements ApplicationListener {
@Override @Override
public void resize(int w0, int h0) { 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"); float magn = (float) getConfigDouble("screenmagnifying");
int width = Math.round(w / magn); int width = (int) Math.floor(w0 / magn);
int height = Math.round(h / magn); int height = (int) Math.floor(h0 / magn);
printdbg(this, "Resize called: "+width+","+height); printdbg(this, "Resize called: "+width+","+height);
@@ -782,7 +788,7 @@ public class App implements ApplicationListener {
//initViewPort(width, height); //initViewPort(width, height);
scr.setDimension(width, height, magn, w, h); scr.setDimension(width, height, magn);
if (currentScreen != null) currentScreen.resize(scr.getWidth(), scr.getHeight()); if (currentScreen != null) currentScreen.resize(scr.getWidth(), scr.getHeight());
TerrarumPostProcessor.INSTANCE.resize(scr.getWidth(), scr.getHeight()); TerrarumPostProcessor.INSTANCE.resize(scr.getWidth(), scr.getHeight());
@@ -855,7 +861,7 @@ public class App implements ApplicationListener {
fullscreenQuad.dispose(); fullscreenQuad.dispose();
logoBatch.dispose(); logoBatch.dispose();
batch.dispose(); batch.dispose();
shapeRender.dispose(); // shapeRender.dispose();
fontGame.dispose(); fontGame.dispose();
fontGameFBO.dispose(); fontGameFBO.dispose();
@@ -1133,6 +1139,11 @@ public class App implements ApplicationListener {
public static RunningEnvironment environment; public static RunningEnvironment environment;
/** defaultDir + "/Recycled/Players" */
public static String recycledPlayersDir;
/** defaultDir + "/Recycled/Worlds" */
public static String recycledWorldsDir;
private static void getDefaultDirectory() { private static void getDefaultDirectory() {
String OS = OSName.toUpperCase(); String OS = OSName.toUpperCase();
if (OS.contains("WIN")) { if (OS.contains("WIN")) {
@@ -1162,6 +1173,8 @@ public class App implements ApplicationListener {
worldsDir = defaultDir + "/Worlds"; worldsDir = defaultDir + "/Worlds";
configDir = defaultDir + "/config.json"; configDir = defaultDir + "/config.json";
loadOrderDir = defaultDir + "/LoadOrder.txt"; 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.name = %s (with identifier %s)", OSName, operationSystem));
System.out.println(String.format("os.version = %s", OSVersion)); System.out.println(String.format("os.version = %s", OSVersion));
@@ -1171,10 +1184,12 @@ public class App implements ApplicationListener {
private static void createDirs() { private static void createDirs() {
File[] dirs = { File[] dirs = {
new File(saveDir), // new File(saveDir),
new File(saveSharedDir), new File(saveSharedDir),
new File(playersDir), new File(playersDir),
new File(worldsDir) new File(worldsDir),
new File(recycledPlayersDir),
new File(recycledWorldsDir),
}; };
for (File it : dirs) { for (File it : dirs) {

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum package net.torvald.terrarum
import com.badlogic.gdx.Gdx
import net.torvald.unicode.BULLET import net.torvald.unicode.BULLET
import net.torvald.unicode.ENDASH import net.torvald.unicode.ENDASH
@@ -223,6 +224,22 @@ Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
""").split('\n') """).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 val gpl3: List<String>; get() = """ GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007

View File

@@ -10,6 +10,9 @@ import com.badlogic.gdx.Input
object DefaultConfig { object DefaultConfig {
val hashMap = hashMapOf<String, Any>( val hashMap = hashMapOf<String, Any>(
"jvm_xmx" to 4,
"jvm_extra_cmd" to "",
"displayfps" to 0, // 0: no limit, non-zero: limit "displayfps" to 0, // 0: no limit, non-zero: limit
"displayfpsidle" to 0, // 0: no limit, non-zero: limit "displayfpsidle" to 0, // 0: no limit, non-zero: limit
"displaycolourdepth" to 8, "displaycolourdepth" to 8,
@@ -19,9 +22,9 @@ object DefaultConfig {
"atlastexsize" to 2048, "atlastexsize" to 2048,
"language" to App.getSysLang(), "language" to App.getSysLang(),
"notificationshowuptime" to 4096, // 4s "notificationshowuptime" to 4000, // 4s
"selecteditemnameshowuptime" to 4096, // 4s "selecteditemnameshowuptime" to 4000, // 4s
"autosaveinterval" to 262144, // 4m22s "autosaveinterval" to 300000, // 5s
"multithread" to true, "multithread" to true,
"showhealthmessageonstartup" 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.Actor
import net.torvald.terrarum.gameactors.ActorID import net.torvald.terrarum.gameactors.ActorID
import net.torvald.terrarum.gameactors.ActorWithBody 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.gameactors.BlockMarkerActor
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
@@ -26,6 +27,8 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.* import java.util.*
import java.util.concurrent.locks.Lock import java.util.concurrent.locks.Lock
import java.util.function.Consumer 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 = override fun getMax(axis: Int, t: ActorWithBody): Double =
when (axis) { when (axis) {
0 -> t.hitbox.endX 0 -> t.hitbox.endX - PHYS_EPSILON_DIST
1 -> t.hitbox.endY 1 -> t.hitbox.endY - PHYS_EPSILON_DIST
else -> throw IllegalArgumentException("nonexistent axis $axis for ${dimensions}-dimensional object") 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 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` * 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 // do not overwrite clean .2 with dirty .1
val flags3 = FileInputStream(file3).let { it.skip(49L); val r = it.read(); it.close(); r } 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 } 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) {} } catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try { try {
// do not overwrite clean .2 with dirty .1 // do not overwrite clean .2 with dirty .1
val flags2 = FileInputStream(file2).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 }
val flags1 = FileInputStream(file1).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) {} } catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try { try {
if (file2.exists() && !file3.exists()) 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()) 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) file.copyTo(file1, true)
} catch (e: IOException) {} } 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 // simple euclidean norm, squared
private val actorDistanceCalculator = DistanceCalculator<ActorWithBody> { t: ActorWithBody, p: PointND -> 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 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() 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> { fun getActorsAt(worldX: Double, worldY: Double): List<ActorWithBody> {
val outList = ArrayList<ActorWithBody>() val outList = ArrayList<ActorWithBody>()
try { try {
actorsRTree.find(worldX, worldY, worldX + 1.0, worldY + 1.0, outList) actorsRTree.find(worldX, worldY, worldX, worldY, outList)
} }
catch (e: NullPointerException) {} catch (e: NullPointerException) {}
return outList return outList

View File

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

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum package net.torvald.terrarum
import net.torvald.random.XXHash32
import org.dyn4j.geometry.Vector2 import org.dyn4j.geometry.Vector2
/** /**
@@ -115,6 +116,10 @@ class Point2i() {
return this return this
} }
override fun hashCode(): Int = XXHash32.hashGeoCoord(x, y)
override fun toString() = "Point2i($x, $y)"
operator fun component1() = x operator fun component1() = x
operator fun component2() = y 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.savegame.VDFileID.SAVEGAMEINFO
import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.ui.UICanvas 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.terrarum.worlddrawer.WorldCamera
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import net.torvald.unsafe.UnsafeHelper import net.torvald.unsafe.UnsafeHelper
@@ -62,11 +64,11 @@ object Terrarum : Disposable {
*/ */
const val PLAYER_REF_ID: Int = 0x91A7E2 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) shapeRender.begin(shapeRendererType)
action(shapeRender) action(shapeRender)
shapeRender.end() shapeRender.end()
} }*/
var blockCodex = BlockCodex(); internal set var blockCodex = BlockCodex(); internal set
@@ -628,6 +630,7 @@ fun Float.sqrt() = FastMath.sqrt(this)
fun Int.abs() = this.absoluteValue fun Int.abs() = this.absoluteValue
fun Double.bipolarClamp(limit: Double) = this.coerceIn(-limit, limit) fun Double.bipolarClamp(limit: Double) = this.coerceIn(-limit, limit)
fun Boolean.toInt(shift: Int = 0) = if (this) 1.shl(shift) else 0 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 Int.bitCount() = java.lang.Integer.bitCount(this)
fun Long.bitCount() = java.lang.Long.bitCount(this) fun Long.bitCount() = java.lang.Long.bitCount(this)
@@ -797,15 +800,17 @@ fun AppUpdateListOfSavegames() {
} }
}.sortedByDescending { it.getLastModifiedTime() }.forEachIndexed { index, it -> }.sortedByDescending { it.getLastModifiedTime() }.forEachIndexed { index, it ->
println("${index+1}.\t${it.diskFile.absolutePath}") 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 jsonFile = it.getFile(SAVEGAMEINFO)!!
val json = JsonReader().parse(ByteArray64Reader(jsonFile.bytes, Common.CHARSET).readText()) var worldUUID: UUID? = null
val worldUUID = UUID.fromString(json.getString("worldIndex")) 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 multiple valid savegames with same UUID exist, only the most recent one is retained
if (!App.savegameWorlds.contains(worldUUID)) { 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.savegameWorldsName[worldUUID] = it.getDiskName(Common.CHARSET)
App.sortedSavegameWorlds.add(worldUUID) App.sortedSavegameWorlds.add(worldUUID)
} }
@@ -827,15 +832,17 @@ fun AppUpdateListOfSavegames() {
} }
}.sortedByDescending { it.getLastModifiedTime() }.forEachIndexed { index, it -> }.sortedByDescending { it.getLastModifiedTime() }.forEachIndexed { index, it ->
println("${index+1}.\t${it.diskFile.absolutePath}") 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 jsonFile = it.getFile(SAVEGAMEINFO)!!
val json = JsonReader().parse(ByteArray64Reader(jsonFile.bytes, Common.CHARSET).readText()) var playerUUID: UUID? = null
val playerUUID = UUID.fromString(json.getString("uuid")) 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 multiple valid savegames with same UUID exist, only the most recent one is retained
if (!App.savegamePlayers.contains(playerUUID)) { 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.savegamePlayersName[playerUUID] = it.getDiskName(Common.CHARSET)
App.sortedPlayers.add(playerUUID) 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 const val DEFAULT_LOADORDER_FILE = """# Load Order
# Modules are loaded from top to bottom. # 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 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 tvSafeActionWidth: Int; get() = Math.round(width * TV_SAFE_ACTION)
val tvSafeActionHeight: Int; get() = Math.round(height * 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 var windowW: Int = 0; private set
/** Apparent window size. `roundToEven(height * magn)` */
var windowH: Int = 0; private set var windowH: Int = 0; private set
init { 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 width = scrw and 0x7FFFFFFE
height = scrh and 0x7FFFFFFE height = scrh and 0x7FFFFFFE
wf = scrw.toFloat() wf = scrw.toFloat()
@@ -54,11 +56,11 @@ class TerrarumScreenSize(scrw: Int = defaultW, scrh: Int = defaultH) {
this.magn = magn this.magn = magn
windowW = ww windowW = (scrw * magn).ceilInt() and 0x7FFFFFFE
windowH = wh 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) printStackTrace(this)
} }

View File

@@ -151,4 +151,8 @@ class WireCodex {
printdbg(this, "Setting prop ${prop.id} ->>\t${prop.nameKey}") 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) PixmapIO2.writeTGA(Gdx.files.absolute(App.defaultDir + "/Exports/${args[1]}.tga"), p, true)
p.dispose() p.dispose()
} }
IngameRenderer.screencapRequested = true IngameRenderer.requestScreencap()
Echo("FBO exported to$ccG Exports/${args[1]}.tga") Echo("FBO exported to$ccG Exports/${args[1]}.tga")
} }
else { else {

View File

@@ -13,10 +13,20 @@ import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.floor 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 { class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = PhysProperties.MOBILE_OBJECT), NoSerialise {
enum class MarkerMode {
FIXTURE_GHOST, BLOCK_MARKER
}
private val defaultSize = 16.0 private val defaultSize = 16.0
override var referenceID: ActorID = 2147483647 // custom refID override var referenceID: ActorID = 2147483647 // custom refID
@@ -29,7 +39,7 @@ class BlockMarkerActor : ActorWithBody(Actor.RenderOrder.OVERLAY, physProp = Phy
get() = CommonResourcePool.getAsTextureRegionPack("blockmarkings_common") get() = CommonResourcePool.getAsTextureRegionPack("blockmarkings_common")
private var ghost: SpriteAnimation? = null private var ghost: SpriteAnimation? = null
private var hasGhost = false var markerMode: MarkerMode = MarkerMode.FIXTURE_GHOST
var ghostColour = Color.WHITE 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 renderOrder = Actor.RenderOrder.OVERLAY // for some reason the constructor didn't work
} }
override fun drawBody(batch: SpriteBatch) { override fun drawBody(batch: SpriteBatch) {
if (isVisible) { if (isVisible) {
if (hasGhost) { if (markerMode == MarkerMode.FIXTURE_GHOST) {
if (INGAME.actorNowPlaying != null) { if (INGAME.actorNowPlaying != null) {
mouseInInteractableRange(INGAME.actorNowPlaying!!) { mouseInInteractableRange(INGAME.actorNowPlaying!!) {
batch.shader = App.shaderGhastlyWhite 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.shader = null
batch.color = markerColour batch.color = markerColour
batch.draw(blockMarkings.get(markerShape, 0), hitbox.startX.toFloat(), hitbox.startY.toFloat()) 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) { fun setGhost(actor: ActorWithBody) {
ghost = actor.sprite ghost = actor.sprite
hasGhost = true markerMode = MarkerMode.FIXTURE_GHOST
hitbox.setDimension(actor.baseHitboxW.toDouble(), actor.baseHitboxH.toDouble()) hitbox.setDimension(actor.baseHitboxW.toDouble(), actor.baseHitboxH.toDouble())
} }
fun unsetGhost() { fun unsetGhost() {
ghost = null ghost = null
hasGhost = false
setGhostColourNone() setGhostColourNone()
hitbox.setDimension(TILE_SIZED, TILE_SIZED) hitbox.setDimension(TILE_SIZED, TILE_SIZED)
} }

View File

@@ -39,10 +39,12 @@ class SimpleGameWorld : GameWorld() {
override lateinit var layerTerrain: BlockLayer 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 worldCreator: UUID = UUID(0L,0L) // TODO record a value to this
var width: Int = 999; private set var width: Int = 999; private set
var height: Int = 999; private set var height: Int = 999; private set
@@ -387,14 +389,14 @@ open class GameWorld() : Disposable {
return wiringGraph[blockAddr]?.get(itemID)?.emt 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 (x, y) = coerceXY(x, y)
val blockAddr = LandUtil.getBlockAddr(this, 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>? { fun getWireReceptionStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): ArrayList<WireReceptionState>? {
return wiringGraph[blockAddr]?.get(itemID)?.rcv return wiringGraph[blockAddr]?.get(itemID)?.rcp
} }
fun setWireGraphOf(x: Int, y: Int, itemID: ItemID, cnx: Int) { fun setWireGraphOf(x: Int, y: Int, itemID: ItemID, cnx: Int) {
@@ -427,7 +429,7 @@ open class GameWorld() : Disposable {
wiringGraph[blockAddr]!![itemID]!!.emt.set(vector) 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 (x, y) = coerceXY(x, y)
val blockAddr = LandUtil.getBlockAddr(this, x, y) val blockAddr = LandUtil.getBlockAddr(this, x, y)
return addWireRecvStateOfUnsafe(blockAddr, itemID, state) return addWireRecvStateOfUnsafe(blockAddr, itemID, state)
@@ -439,13 +441,13 @@ open class GameWorld() : Disposable {
return clearAllWireRecvStateUnsafe(blockAddr) return clearAllWireRecvStateUnsafe(blockAddr)
} }
fun addWireRecvStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, state: WireRecvState) { fun addWireRecvStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, state: WireReceptionState) {
if (wiringGraph[blockAddr] == null) if (wiringGraph[blockAddr] == null)
wiringGraph[blockAddr] = WiringGraphMap() wiringGraph[blockAddr] = WiringGraphMap()
if (wiringGraph[blockAddr]!![itemID] == null) if (wiringGraph[blockAddr]!![itemID] == null)
wiringGraph[blockAddr]!![itemID] = WiringSimCell(0) 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>? { fun getAllWiringGraph(x: Int, y: Int): HashMap<ItemID, WiringSimCell>? {
@@ -460,7 +462,7 @@ open class GameWorld() : Disposable {
fun clearAllWireRecvStateUnsafe(blockAddr: BlockAddress) { fun clearAllWireRecvStateUnsafe(blockAddr: BlockAddress) {
wiringGraph[blockAddr]?.forEach { 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? 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 dist: Int = -1, // how many tiles it took to traverse
var src: Point2i = Point2i(0,0) // xy position 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 // 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( data class WiringSimCell(
var cnx: Int = 0, // connections. [1, 2, 4, 8] = [RIGHT, DOWN, LEFT, UP] 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 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? { 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 com.badlogic.gdx.utils.Queue
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE 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.Block
import net.torvald.terrarum.blockproperties.Fluid import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameactors.ActorWithBody import net.torvald.terrarum.gameactors.ActorWithBody
@@ -468,14 +469,15 @@ object WorldSimulator {
/** /**
* @return List of FixtureBases, safe to cast into Electric * @return List of FixtureBases, safe to cast into Electric
*/ */
private fun wiresimGetSourceBlocks(): List<FixtureBase> = private fun wiresimGetSourceBlocks(): List<Electric> =
INGAME.actorContainerActive.filterIsInstance<FixtureBase>().filter { INGAME.actorContainerActive.filterIsInstance<Electric>().filter {
it is Electric && it.inUpdateRange(world) && it.wireEmitterTypes.isNotEmpty() it.inUpdateRange(world) && it.wireEmitterTypes.isNotEmpty()
} }
private val wireSimMarked = HashSet<Long>() private val wireSimMarked = HashSet<Long>()
private val wireSimPoints = Queue<WireGraphCursor>() private val wireSimPoints = Queue<WireGraphCursor>()
private val oldTraversedNodes = ArrayList<WireGraphCursor>() private val oldTraversedNodes = ArrayList<WireGraphCursor>()
private val fixtureCache = HashMap<Point2i, Pair<Electric, WireEmissionType>>() // also instance of Electric
private fun simulateWires(delta: Float) { private fun simulateWires(delta: Float) {
// unset old wires before we begin // unset old wires before we begin
@@ -484,14 +486,15 @@ object WorldSimulator {
} }
oldTraversedNodes.clear() oldTraversedNodes.clear()
fixtureCache.clear()
wiresimGetSourceBlocks().let { sources -> wiresimGetSourceBlocks().let { sources ->
// signal-emitting fixtures must set emitState of its own tiles via update() // signal-emitting fixtures must set emitState of its own tiles via update()
sources.forEach { sources.forEach {
(it as Electric).wireEmitterTypes.forEach { wireType, bbi -> it.wireEmitterTypes.forEach { bbi, wireType ->
val startingPoint = it.worldBlockPos!! + it.blockBoxIndexToPoint2i(bbi) 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 -> world.getAllWiringGraph(startingPoint.x, startingPoint.y)?.keys?.filter { WireCodex[it].accepts == wireType }?.forEach { wire ->
val simStartingPoint = WireGraphCursor(startingPoint, wire) val simStartingPoint = WireGraphCursor(startingPoint, wire)
@@ -506,6 +509,8 @@ object WorldSimulator {
private fun traverseWireGraph(world: GameWorld, wire: ItemID, startingPoint: WireGraphCursor, signal: Vector2) { private fun traverseWireGraph(world: GameWorld, wire: ItemID, startingPoint: WireGraphCursor, signal: Vector2) {
val emissionType = WireCodex[wire].accepts
fun getAdjacent(cnx: Int, point: WireGraphCursor): List<WireGraphCursor> { fun getAdjacent(cnx: Int, point: WireGraphCursor): List<WireGraphCursor> {
val r = ArrayList<WireGraphCursor>() val r = ArrayList<WireGraphCursor>()
for (dir in intArrayOf(RIGHT, DOWN, LEFT, UP)) { for (dir in intArrayOf(RIGHT, DOWN, LEFT, UP)) {
@@ -540,6 +545,28 @@ object WorldSimulator {
enq(x) 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 com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.* 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_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.gameactors.ActorWithBody 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.gameworld.fmod
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.weather.WeatherMixer import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.weather.WeatherMixer.render
import net.torvald.terrarum.worlddrawer.BlocksDrawer import net.torvald.terrarum.worlddrawer.BlocksDrawer
import net.torvald.terrarum.worlddrawer.FeaturesDrawer import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.worlddrawer.LightmapRenderer import net.torvald.terrarum.worlddrawer.LightmapRenderer
@@ -204,7 +205,7 @@ object IngameRenderer : Disposable {
actorsRenderOverlay: List<ActorWithBody>, actorsRenderOverlay: List<ActorWithBody>,
particlesContainer : CircularArray<ParticleBase>, particlesContainer : CircularArray<ParticleBase>,
player: ActorWithBody? = null, player: ActorWithBody? = null,
uiContainer: UIContainer? = null uiContainer: UIContainer? = null,
) { ) {
renderingActorsCount = (actorsRenderBehind.size) + renderingActorsCount = (actorsRenderBehind.size) +
(actorsRenderMiddle.size) + (actorsRenderMiddle.size) +
@@ -365,13 +366,19 @@ object IngameRenderer : Disposable {
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
if (screencapRequested) { if (screencapRequested) {
screencapRequested = false printdbg(this, "Screencap was requested, processing...")
var hasError = false
try { try {
screencapExportCallback(fboMixedOut) screencapExportCallback(fboMixedOut)
} }
catch (e: Throwable) { catch (e: Throwable) {
printdbgerr(this, "An error occured while taking screencap:")
e.printStackTrace() 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! * This "screencap" will capture the game WITHOUT gui and postprocessors!
* To capture the entire game, use [App.requestScreenshot] * To capture the entire game, use [App.requestScreenshot]
*/ */
@Volatile internal var screencapRequested = false @Volatile private var screencapRequested = false
@Volatile internal var fboRGBexportedLatch = false @Volatile internal var screencapBusy = false; private set
@Volatile internal var screencapExportCallback: (FrameBuffer) -> Unit = {} @Volatile internal var screencapExportCallback: (FrameBuffer) -> Unit = {}
@Volatile internal lateinit var fboRGBexport: Pixmap @Volatile internal lateinit var fboRGBexport: Pixmap
fun requestScreencap() {
screencapRequested = true
screencapBusy = true
printdbg(this, "requestScreencap called from:")
printStackTrace(this)
}
private fun drawToRGB( private fun drawToRGB(
actorsRenderBehind: List<ActorWithBody>?, actorsRenderBehind: List<ActorWithBody>?,
actorsRenderMiddle: 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.Gdx
import com.badlogic.gdx.Input import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Camera import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.* 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.gameparticles.ParticleBase
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldSimulator import net.torvald.terrarum.gameworld.WorldSimulator
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.* import net.torvald.terrarum.modulebasegame.gameactors.*
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
import net.torvald.terrarum.modulebasegame.gameitems.PickaxeCore 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.ReadActor
import net.torvald.terrarum.modulebasegame.serialise.WriteSavegame import net.torvald.terrarum.modulebasegame.serialise.WriteSavegame
import net.torvald.terrarum.modulebasegame.ui.* 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.RoguelikeRandomiser
import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen
import net.torvald.terrarum.modulebasegame.worldgenerator.WorldgenParams 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.savegame.VirtualDisk
import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.Toolkit.hdrawWidth
import net.torvald.terrarum.ui.UIAutosaveNotifier import net.torvald.terrarum.ui.UIAutosaveNotifier
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.weather.WeatherMixer import net.torvald.terrarum.weather.WeatherMixer
@@ -53,6 +57,7 @@ import net.torvald.unicode.EMDASH
import net.torvald.util.CircularArray import net.torvald.util.CircularArray
import org.khelekore.prtree.PRTree import org.khelekore.prtree.PRTree
import java.util.* import java.util.*
import kotlin.math.roundToInt
/** /**
@@ -260,7 +265,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
} }
IngameRenderer.setRenderedWorld(world) IngameRenderer.setRenderedWorld(world)
blockMarkingActor.isVisible = true
super.show() // this function sets gameInitialised = 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 // 2. cannot sync up the "counter" to determine whether both are finished
uiAutosaveNotifier.setAsOpen() uiAutosaveNotifier.setAsOpen()
val saveTime_t = App.getTIME_T() val saveTime_t = App.getTIME_T()
printdbg(this, "Immediate Save")
WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, true, autosaveOnErrorAction) { WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, true, autosaveOnErrorAction) {
printdbg(this, "immediate save callback from PLAYER")
makeSavegameBackupCopy(getPlayerSaveFiledesc(playerSavefileName)) makeSavegameBackupCopy(getPlayerSaveFiledesc(playerSavefileName))
WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.WORLD, worldDisk, getWorldSaveFiledesc(worldSavefileName), this, true, autosaveOnErrorAction) { 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 makeSavegameBackupCopy(getWorldSaveFiledesc(worldSavefileName)) // don't put it on the postInit() or render(); must be called using callback
uiAutosaveNotifier.setAsClose() uiAutosaveNotifier.setAsClose()
} }
@@ -470,13 +480,14 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
world.worldCreator = UUID.fromString(player.uuid.toString()) world.worldCreator = UUID.fromString(player.uuid.toString())
printdbg(this, "new woridIndex: ${world.worldIndex}") printdbg(this, "new worldIndex: ${world.worldIndex}")
printdbg(this, "worldCurrentlyPlaying: ${player.worldCurrentlyPlaying}") printdbg(this, "worldCurrentlyPlaying: ${player.worldCurrentlyPlaying}")
actorNowPlaying = player actorNowPlaying = player
actorGamer = player actorGamer = player
forceAddActor(player) forceAddActor(player)
WeatherMixer.internalReset()
} }
KeyToggler.forceSet(Input.Keys.Q, false) KeyToggler.forceSet(Input.Keys.Q, false)
@@ -521,7 +532,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// pie menu // pie menu
uiPieMenu = UIQuickslotPie() uiPieMenu = UIQuickslotPie()
uiPieMenu.setPosition(drawWidth / 2, App.scr.halfh) uiPieMenu.setPosition(hdrawWidth, App.scr.halfh)
// vital metre // vital metre
// fill in getter functions by // fill in getter functions by
@@ -715,6 +726,10 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
gameUpdateGovernor.reset() gameUpdateGovernor.reset()
if (UILoadGovernor.previousSaveWasLoaded) {
sendNotification(listOf(Lang["GAME_PREV_SAVE_WAS_LOADED1"], Lang["GAME_PREV_SAVE_WAS_LOADED2"]))
}
gameFullyLoaded = true gameFullyLoaded = true
} }
@@ -851,7 +866,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
//notifier.update(delta) //notifier.update(delta)
// open/close fake blur UI according to what's opened // open/close fake blur UI according to what's opened
if (uiInventoryPlayer.isVisible || if (uiInventoryPlayer.isVisible ||
getUIFixture.get()?.isVisible == true) { getUIFixture.get()?.isVisible == true || worldTransitionOngoing) {
uiBlur.setAsOpen() uiBlur.setAsOpen()
} }
else { else {
@@ -867,6 +882,26 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
//println("paused = $paused") //println("paused = $paused")
if ((!paused && !App.isScreenshotRequested()) && newWorldLoadedLatch) newWorldLoadedLatch = false 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, actorNowPlaying,
uiContainer// + uiFixture 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 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() uiAutosaveNotifier.setAsOpen()
val saveTime_t = App.getTIME_T() val saveTime_t = App.getTIME_T()
val playerSavefile = getPlayerSaveFiledesc(INGAME.playerSavefileName) val playerSavefile0 = getPlayerSaveFiledesc(INGAME.playerSavefileName)
val worldSavefile = getWorldSaveFiledesc(INGAME.worldSavefileName) 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) { 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) { WriteSavegame(saveTime_t, WriteSavegame.SaveMode.QUICK_WORLD, INGAME.worldDisk, worldSavefile, INGAME as TerrarumIngame, true, autosaveOnErrorAction) {
// callback: // callback:
// rebuild the disk skimmers // rebuild the disk skimmers

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.modulebasegame package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.InputAdapter import com.badlogic.gdx.InputAdapter
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.OrthographicCamera
@@ -13,17 +14,20 @@ import net.torvald.random.HQRNG
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.App.printdbgerr 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_SIZED
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.console.CommandDict import net.torvald.terrarum.console.CommandDict
import net.torvald.terrarum.gameactors.* import net.torvald.terrarum.gameactors.*
import net.torvald.terrarum.gameactors.ai.ActorAI import net.torvald.terrarum.gameactors.ai.ActorAI
import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.gameparticles.ParticleBase import net.torvald.terrarum.gameparticles.ParticleBase
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.gameworld.fmod import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.langpack.Lang 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.UIRemoCon
import net.torvald.terrarum.modulebasegame.ui.UITitleRemoConYaml import net.torvald.terrarum.modulebasegame.ui.UITitleRemoConYaml
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
@@ -64,49 +68,72 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
private lateinit var demoWorld: GameWorld private lateinit var demoWorld: GameWorld
private lateinit var cameraNodes: FloatArray // camera Y-pos 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 val cameraAI = object : ActorAI {
private var firstTime = true 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) { override fun update(actor: Actor, delta: Float) {
val ww = TILE_SIZEF * demoWorld.width val ww = TILE_SIZEF * demoWorld.width
val actor = actor as CameraPlayer val actor = actor as CameraPlayer
val px: Double = actor.hitbox.canonicalX + actor.actorValue.getAsDouble(AVKey.SPEED)!! val stride = cos(actor.bearing1A) * actor.actorValue.getAsDouble(AVKey.SPEED)!!
val pxP = px - lookaheadDist * cos(actor.targetBearing)
val pxN = px + lookaheadDist * cos(actor.targetBearing)
val yP = getPointAt(pxP) val x1 = actor.hitbox.startX// + stride
val yN = getPointAt(pxN) 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) { if (firstTime) {
firstTime = false firstTime = false
actor.hitbox.setPositionY(y - 8.0) actor.hitbox.setPosition(x1, getPointAt(x1))
} }
else { else {
//actor.moveTo(px, y - 8.0) actor.bearing1A = atan2(py1C - py1L, px1C - px1L)
//actor.hitbox.setPosition(px, y - 8.0) actor.bearing1B = atan2(py1R - py1C, px1R - px1C)
actor.moveTo(atan2((yN - yP).toDouble(), pxN - pxP)) 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()) actor.hitbox.translatePosX(-ww.toDouble())
} }
} }
} }
private lateinit var cameraPlayer: ActorWithBody private lateinit var cameraPlayer: ActorWithBody
@@ -122,9 +149,9 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
private lateinit var worldFBO: FloatFrameBuffer private lateinit var worldFBO: FloatFrameBuffer
private val warning32bitJavaIcon = TextureRegion(Texture(Gdx.files.internal("assets/graphics/gui/32_bit_warning.tga"))) 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 { init {
warning32bitJavaIcon.flip(false, false)
gameUpdateGovernor = ConsistentUpdateRate.also { it.reset() } gameUpdateGovernor = ConsistentUpdateRate.also { it.reset() }
} }
@@ -138,7 +165,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
//ReadWorld.readWorldAndSetNewWorld(Terrarum.ingame!! as TerrarumIngame, reader) //ReadWorld.readWorldAndSetNewWorld(Terrarum.ingame!! as TerrarumIngame, reader)
val world = ReadSimpleWorld(reader, file) val world = ReadSimpleWorld(reader, file)
demoWorld = world demoWorld = world
demoWorld.worldTime.timeDelta = 0//60 demoWorld.worldTime.timeDelta = 330 // a year = 8 minutes
printdbg(this, "Demo world loaded") printdbg(this, "Demo world loaded")
} }
catch (e: IOException) { catch (e: IOException) {
@@ -152,7 +179,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32) demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32)
// construct camera nodes // construct camera nodes
val nodeCount = demoWorld.width / 10 val nodeCount = demoWorld.width / cameraNodeWidth
cameraNodes = kotlin.FloatArray(nodeCount) { cameraNodes = kotlin.FloatArray(nodeCount) {
val tileXPos = (demoWorld.width.toFloat() * it / nodeCount).floorInt() val tileXPos = (demoWorld.width.toFloat() * it / nodeCount).floorInt()
var travelDownCounter = 0 var travelDownCounter = 0
@@ -237,6 +264,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
printdbg(this, "update list of savegames") printdbg(this, "update list of savegames")
// to show "Continue" and "Load" on the titlescreen, uncomment this line // to show "Continue" and "Load" on the titlescreen, uncomment this line
App.updateListOfSavegames() App.updateListOfSavegames()
UILoadGovernor.reset()
loadThingsWhileIntroIsVisible() loadThingsWhileIntroIsVisible()
@@ -253,9 +281,14 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
} }
private val updateScreen = { delta: Float -> 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) demoWorld.updateWorldTime(delta)
WeatherMixer.update(delta, cameraPlayer, demoWorld) // WeatherMixer.update(delta, cameraPlayer, demoWorld)
WeatherMixer.forceTimeAt = forcedTime
cameraPlayer.update(delta) cameraPlayer.update(delta)
// worldcamera update AFTER cameraplayer in this case; the other way is just an exception for actual ingame SFX // 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 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 -> private val renderScreen = { delta: Float ->
Gdx.graphics.setTitle(TerrarumIngame.getCanonicalTitle()) Gdx.graphics.setTitle(TerrarumIngame.getCanonicalTitle())
@@ -290,6 +341,52 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
particles, particles,
uiContainer = uiContainer 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 { else {
printdbgerr(this, "Demoworld is already been destroyed") printdbgerr(this, "Demoworld is already been destroyed")
@@ -332,16 +429,34 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
batch.color = Color.WHITE batch.color = Color.WHITE
if (App.is32BitJVM && uiRemoCon.currentRemoConContents.parent == null) { // warn: 32-bit
// if (uiRemoCon.currentRemoConContents.parent == null) { val linegap = 4
val linegap = 4 val imgTxtGap = 10
val imgTxtGap = 10 val yoff = App.scr.height - App.scr.tvSafeGraphicsHeight - 64 - (3*(20+linegap)) - imgTxtGap - 9
val yoff = App.scr.height - App.scr.tvSafeGraphicsHeight - 64 - (3*(20+linegap)) - imgTxtGap - 9 if (uiRemoCon.currentRemoConContents.parent == null) {
Toolkit.drawCentered(batch, warning32bitJavaIcon, yoff) var texts = emptyList<String>()
for (i in 0..2) { var textcols = emptyList<Color>()
val text = Lang.get("GAME_32BIT_WARNING${i+1}", (i != 2)) if (App.is32BitJVM) {
if (i == 2) batch.color = Toolkit.Theme.COL_SELECTED Toolkit.drawCentered(batch, warning32bitJavaIcon, yoff)
App.fontGame.draw(batch, text, ((drawWidth - App.fontGame.getWidth(text)) / 2).toFloat(), yoff + imgTxtGap + 64f + linegap + i*(20+linegap)) 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() uiRemoCon.dispose()
demoWorld.dispose() demoWorld.dispose()
warning32bitJavaIcon.texture.dispose() warning32bitJavaIcon.texture.dispose()
warningAppleRosettaIcon.texture.dispose()
} }
override fun inputStrobed(e: TerrarumKeyboardEvent) { 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) override val hitbox = Hitbox(0.0, 0.0, 2.0, 2.0)
init { 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( hitbox.setPosition(
HQRNG().nextInt(demoWorld.width) * TILE_SIZED, HQRNG().nextInt(demoWorld.width) * TILE_SIZED,
0.0 // Y pos: placeholder; camera AI will take it over 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 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) { override fun moveTo(bearing: Double) {
targetBearing = bearing targetBearing = bearing
@@ -495,6 +618,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
val xdiff = if (xdiff1 < 0) xdiff2 else xdiff1 val xdiff = if (xdiff1 < 0) xdiff2 else xdiff1
val ydiff = toY - hitbox.canonicalY val ydiff = toY - hitbox.canonicalY
moveTo(atan2(ydiff, xdiff)) moveTo(atan2(ydiff, xdiff))
hitbox.setPositionX(hitbox.canonicalX % ww) hitbox.setPositionX(hitbox.canonicalX % ww)
} }

View File

@@ -14,11 +14,111 @@ import org.dyn4j.geometry.Vector2
import java.util.* import java.util.*
typealias BlockBoxIndex = Int typealias BlockBoxIndex = Int
typealias WireEmissionType = String
interface Electric { open class Electric : FixtureBase {
val wireEmitterTypes: HashMap<String, BlockBoxIndex>
val wireEmission: HashMap<BlockBoxIndex, Vector2> protected constructor() : super() {
val wireConsumption: HashMap<BlockBoxIndex, Vector2> 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 protected set
lateinit var blockBox: BlockBox // something like TapestryObject will want to redefine this 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) } 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 nameFun: () -> String = { "" }
@Transient var mainUI: UICanvas? = null @Transient var mainUI: UICanvas? = null
var inventory: FixtureInventory? = null var inventory: FixtureInventory? = null
protected var actorThatInstalledThisFixture: UUID? = 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. * Standard 32-bit binary flags.
* *
* (LSB) * (LSB)
* - 0: fluid resist - when FALSE, the fixture will break itself to item/nothing. * - 0: fluid intolerance - when SET, the fixture will break itself to item/nothing (depends on the flag #1).
* For example, crops has this flag FALSE. * For example, crops have this flag SET.
* - 1: don't drop item when broken - when TRUE, the fixture will simply disappear instead of * - 1: no drops - when SET, the fixture will simply disappear instead of dropping itself.
* dropping itself. For example, crop has this flag TRUE. * For example, crops have this flag SET.
* *
* (MSB) * (MSB)
* *

View File

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

View File

@@ -1,22 +1,47 @@
package net.torvald.terrarum.modulebasegame.gameactors 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.gameactors.AVKey
import net.torvald.terrarum.langpack.Lang 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.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.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 net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import org.dyn4j.geometry.Vector2
import java.util.HashMap
/** /**
* Created by minjaesong on 2023-05-28. * Created by minjaesong on 2023-05-28.
*/ */
class FixtureWorldPortal : FixtureBase { class FixtureWorldPortal : Electric {
constructor() : super( constructor() : super(
BlockBox(BlockBox.NO_COLLISION, 5, 2), BlockBox(BlockBox.NO_COLLISION, 5, 2),
nameFun = { Lang["ITEM_WORLD_PORTAL"] }, nameFun = { Lang["ITEM_WORLD_PORTAL"] },
mainUI = UIWorldPortal() mainUI = UIWorldPortal(),
// inventory = FixtureInventory(200, CAPACITY_MODE_WEIGHT)
) { ) {
// TODO do something with (mainUI as UIWorldPortal).*** // 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 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() { override fun reload() {
@@ -37,4 +116,9 @@ class FixtureWorldPortal : FixtureBase {
// TODO do something with (mainUI as UIWorldPortal).*** // 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) { override fun drawBody(batch: SpriteBatch) {
Terrarum.inShapeRenderer { /*Terrarum.inShapeRenderer {
it.color = color it.color = color
it.circle( it.circle(
hitbox.startX.toFloat() - 1f, hitbox.startX.toFloat() - 1f,
@@ -44,7 +44,7 @@ class PhysTestBall : ActorWithBody(RenderOrder.MIDDLE, PhysProperties.PHYSICS_OB
hitbox.startY.toFloat() - 1f, hitbox.startY.toFloat() - 1f,
hitbox.width.toFloat() hitbox.width.toFloat()
) )
} }*/
//println(moveDelta) //println(moveDelta)
} }

View File

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

View File

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

View File

@@ -1,35 +1,5 @@
package net.torvald.terrarum.modulebasegame.serialise 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 class SavingThread(private val errorHandler: (Throwable) -> Unit) : Runnable {
abstract fun save() abstract fun save()
@@ -45,196 +15,4 @@ abstract class SavingThread(private val errorHandler: (Throwable) -> Unit) : Run
} }
} }
/** const val SCREENCAP_WAIT_TRY_MAX = 256
* 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()
}
}

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 disk: VirtualDisk,
val outFile: File, val outFile: File,
val ingame: TerrarumIngame, val ingame: TerrarumIngame,
val hasThumbnail: Boolean,
val isAuto: Boolean, val isAuto: Boolean,
val callback: () -> Unit, val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit val errorHandler: (Throwable) -> Unit
@@ -46,12 +45,17 @@ class QuickSingleplayerWorldSavingThread(
override fun save() { override fun save() {
printdbg(this, "outFile: ${outFile.path}")
val skimmer = DiskSkimmer(outFile) val skimmer = DiskSkimmer(outFile)
if (hasThumbnail) { // wait for screencap
while (!IngameRenderer.fboRGBexportedLatch) { var emergencyStopCnt = 0
Thread.sleep(1L) 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 allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
@@ -76,14 +80,14 @@ class QuickSingleplayerWorldSavingThread(
val creation_t = ingame.world.creationTime val creation_t = ingame.world.creationTime
if (hasThumbnail) { PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true) IngameRenderer.fboRGBexport.dispose()
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 WriteSavegame.saveProgress += 1f
@@ -150,7 +154,7 @@ class QuickSingleplayerWorldSavingThread(
printdbg(this, "Game saved with size of ${outFile.length()} bytes") printdbg(this, "Game saved with size of ${outFile.length()} bytes")
if (hasThumbnail) IngameRenderer.fboRGBexportedLatch = false // IngameRenderer.screencapBusy = false
WriteSavegame.savingStatus = 255 WriteSavegame.savingStatus = 255
ingame.clearModifiedChunks() 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 { object ReadActor {
operator fun invoke(disk: SimpleFileSystem, dataStream: Reader): Actor = operator fun invoke(disk: SimpleFileSystem, dataStream: Reader): Actor =
Common.jsoner.fromJson<Actor?>(null, dataStream).also { try {
fillInDetails(disk, it) 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) { private fun fillInDetails(disk: SimpleFileSystem, actor: Actor) {

View File

@@ -36,43 +36,45 @@ object WriteSavegame {
@Volatile var saveProgress = 0f @Volatile var saveProgress = 0f
@Volatile var saveProgressMax = 1f @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) { 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, hasThumbnail, isAuto, callback, errorHandler) SaveMode.WORLD -> WorldSavingThread(time_t, disk, outFile, ingame, isAuto, callback, errorHandler)
SaveMode.PLAYER -> PlayerSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback, errorHandler) SaveMode.PLAYER -> PlayerSavingThread(time_t, disk, outFile, ingame, isAuto, callback, errorHandler)
SaveMode.QUICK_WORLD -> QuickSingleplayerWorldSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback, errorHandler) SaveMode.QUICK_WORLD -> QuickSingleplayerWorldSavingThread(time_t, disk, outFile, ingame, isAuto, callback, errorHandler)
else -> throw IllegalArgumentException("$mode") 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) { operator fun invoke(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0 savingStatus = 0
val hasThumbnail = (mode == SaveMode.WORLD || mode == SaveMode.QUICK_WORLD)
printdbg(this, "Save queued") printdbg(this, "Save queued")
if (hasThumbnail) { installScreencap()
IngameRenderer.screencapExportCallback = { fb -> try { printdbg(this, "ScreencapExport installed: ${IngameRenderer.screencapExportCallback}") }
printdbg(this, "Generating thumbnail...") catch (e: UninitializedPropertyAccessException) { printdbg(this, "ScreencapExport installed: no") }
IngameRenderer.requestScreencap()
val w = 960 val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
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")
savingThread.start() savingThread.start()
// it is caller's job to keep the game paused or keep a "save in progress" ui up // 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) { fun immediate(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0 savingStatus = 0
printdbg(this, "Immediate save fired") 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() savingThread.start()
// it is caller's job to keep the game paused or keep a "save in progress" ui up // 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}") printdbg(this, "Player localhash: ${player.localHashStr}, hasSprite: ${player.sprite != null}")
val currentWorldId = player.worldCurrentlyPlaying 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) val world = ReadWorld(ByteArray64Reader(worldDisk.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET), worldDisk.diskFile)
world.layerTerrain = BlockLayer(world.width, world.height) 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) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
blendNormalStraightAlpha(batch) blendNormalStraightAlpha(batch)
drawColor.a = handler.opacity fontCol.a = handler.opacity * OPACITY
fontCol.a = handler.opacity
val realTextWidth = 12 + if (message.size == 1) val realTextWidth = 12 + if (message.size == 1)
App.fontGame.getWidth(message[0]) App.fontGame.getWidth(message[0])
@@ -63,12 +60,11 @@ class Notification : UICanvas() {
// force the UI to the centre of the screen // force the UI to the centre of the screen
this.posX = (App.scr.width - displayedTextWidth) / 2 this.posX = (App.scr.width - displayedTextWidth) / 2
val textHeight = message.size * App.fontGame.lineHeight 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 batch.color = fontCol
message.forEachIndexed { index, s -> message.forEachIndexed { index, s ->
@@ -79,7 +75,6 @@ class Notification : UICanvas() {
// dunno why, it doesn't work without this. // dunno why, it doesn't work without this.
drawColor.a = 1f
fontCol.a = 1f fontCol.a = 1f
} }
@@ -111,6 +106,7 @@ class Notification : UICanvas() {
companion object { companion object {
// private int messagesShowingIndex = 0; // 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 activeCol = Color.WHITE
) )
paletteItem.clickOnceListener = { _, _, _ -> paletteItem.clickOnceListener = { _, _ ->
parent.setPencilColour(prop.id) parent.setPencilColour(prop.id)
} }

View File

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

View File

@@ -14,7 +14,6 @@ import net.torvald.terrarum.itemproperties.CraftingCodex
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory
import net.torvald.terrarum.modulebasegame.gameactors.InventoryPair 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.modulebasegame.ui.UIItemInventoryItemGrid.Companion.listGap
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas 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!) // make sure grid buttons for ingredients do nothing (even if they are hidden!)
itemListIngredients.gridModeButtons[0].touchDownListener = { _,_,_,_ -> } itemListIngredients.navRemoCon.listButtonListener = { _,_, -> }
itemListIngredients.gridModeButtons[1].touchDownListener = { _,_,_,_ -> } itemListIngredients.navRemoCon.gridButtonListener = { _,_, -> }
itemListIngredients.isCompactMode = true itemListIngredients.isCompactMode = true
itemListIngredients.setCustomHighlightRuleSub { itemListIngredients.setCustomHighlightRuleSub {
it.item?.let { ingredient -> it.item?.let { ingredient ->
@@ -252,8 +251,8 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
} } } } } }
) )
// make grid mode buttons work together // make grid mode buttons work together
// itemListPlayer.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) } // itemListPlayer.gridModeButtons[0].clickOnceListener = { _,_ -> setCompact(false) }
// itemListPlayer.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) } // 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 -> getPlayerInventory().let { player -> recipeClicked?.let { recipe ->
// check if player has enough amount of ingredients // check if player has enough amount of ingredients
val itemCraftable = itemListIngredients.getInventory().itemList.all { (itm, qty) -> val itemCraftable = itemListIngredients.getInventory().itemList.all { (itm, qty) ->
@@ -340,8 +339,8 @@ class UICrafting(val full: UIInventoryFull) : UICanvas(), HasInventory {
refreshCraftButtonStatus() refreshCraftButtonStatus()
} }
// make grid mode buttons work together // make grid mode buttons work together
// itemListCraftable.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) } // itemListCraftable.gridModeButtons[0].clickOnceListener = { _,_ -> setCompact(false) }
// itemListCraftable.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) } // itemListCraftable.gridModeButtons[1].clickOnceListener = { _,_ -> setCompact(true) }
handler.allowESCtoClose = 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 // 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 com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.CommonResourcePool import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.ceilInt import net.torvald.terrarum.ceilInt
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.CELL_COL 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 h1MarginBottom = 4
private val options = arrayOf( 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_dither", { Lang["MENU_OPTIONS_DITHER"] }, "toggle"),
arrayOf("fx_backgroundblur", { Lang["MENU_OPTIONS_BLUR"] }, "toggle"), arrayOf("fx_backgroundblur", { Lang["MENU_OPTIONS_BLUR"] }, "toggle"),
arrayOf("maxparticles", { Lang["MENU_OPTIONS_PARTICLES"] }, "spinner,256,1024,256"), 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("displayfps", { Lang["MENU_LABEL_FRAMESPERSEC"] }, "spinner,0,300,2"),
arrayOf("usevsync", { Lang["MENU_OPTIONS_VSYNC"] }, "toggle"), arrayOf("usevsync", { Lang["MENU_OPTIONS_VSYNC"] }, "toggle"),
arrayOf("", { "(${Lang["MENU_LABEL_RESTART_REQUIRED"]})" }, "p"), 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"), arrayOf("fx_streamerslayout", { Lang["MENU_OPTION_STREAMERS_LAYOUT"] }, "toggle"),
) )
@@ -90,7 +91,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
} }
else if (args.startsWith("toggle")) { else if (args.startsWith("toggle")) {
UIItemToggleButton(this, x, y, spinnerWidth, App.getConfigBoolean(optionName)) to { it: UIItem, optionStr: String -> UIItemToggleButton(this, x, y, spinnerWidth, App.getConfigBoolean(optionName)) to { it: UIItem, optionStr: String ->
(it as UIItemToggleButton).clickOnceListener = { _, _, _ -> (it as UIItemToggleButton).clickOnceListener = { _, _ ->
it.toggle() it.toggle()
App.setConfig(optionStr, it.getStatus()) 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 -> 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 = { (it as UIItemSpinner).selectionChangeListener = {
App.setConfig(optionStr, it) App.setConfig(optionStr, it)
} } }
}
} }
else if (args.startsWith("typeinint")) { else if (args.startsWith("typeinint")) {
// val arg = args.split(',') // args: none // 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 = { (it as UIItemTextLineInput).textCommitListener = {
App.setConfig(optionStr, it.toInt()) // HAXXX!!! App.setConfig(optionStr, it.toInt()) // HAXXX!!!
} }
@@ -122,7 +128,12 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
else if (args.startsWith("typeinres")) { else if (args.startsWith("typeinres")) {
val keyWidth = optionName.substringBefore(',') val keyWidth = optionName.substringBefore(',')
val keyHeight = optionName.substringAfter(',') 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 -> (it as UIItemTextLineInput).textCommitListener = { text ->
val text = text.lowercase() val text = text.lowercase()
if (text.matches(Regex("""[0-9]+x[0-9]+"""))) { if (text.matches(Regex("""[0-9]+x[0-9]+"""))) {
@@ -167,8 +178,11 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
options.forEachIndexed { index, strings -> options.forEachIndexed { index, strings ->
val mode = strings[2] val mode = strings[2]
val font = if (mode == "h1") App.fontUITitle else App.fontGame
val label = (strings[1] as () -> String).invoke() val label = (strings[1] as () -> String).invoke()
val labelWidth = App.fontGame.getWidth(label) val labelWidth = font.getWidth(label)
batch.color = when (mode) { batch.color = when (mode) {
"h1" -> Toolkit.Theme.COL_MOUSE_UP "h1" -> Toolkit.Theme.COL_MOUSE_UP
"p" -> Color.LIGHT_GRAY "p" -> Color.LIGHT_GRAY
@@ -180,7 +194,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() {
else else
drawX + width/2 - panelgap - labelWidth // right aligned at the middle of the panel, offsetted by panelgap 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 // draw hrule
if (mode == "h1") { 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() { override fun dispose() {
} }
} }

View File

@@ -110,14 +110,14 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
private val selDrawX = (Toolkit.drawWidth - selectorWidth) / 2 private val selDrawX = (Toolkit.drawWidth - selectorWidth) / 2
private val halfw = width / 2 private val halfw = width / 2
private val y1 = 400 // private val y1 = 400
private val y2 = y1 + 40 // private val y2 = y1 + 40
private val lowLayerCodes = IME.getAllLowLayers().sorted() private val lowLayerCodes = IME.getAllLowLayers().sorted()
private val lowLayerNames = lowLayerCodes.map { { IME.getLowLayerByName(it).name } } private val lowLayerNames = lowLayerCodes.map { { IME.getLowLayerByName(it).name } }
private val keyboardLayoutSelection = UIItemTextSelector(this, private val keyboardLayoutSelection = UIItemTextSelector(this,
selDrawX + (halfselw - textSelWidth) / 2, selDrawX + (halfselw - textSelWidth) / 2,
y2, kby + 260,
lowLayerNames, lowLayerNames,
lowLayerCodes.linearSearch { it == App.getConfigString("basekeyboardlayout") } ?: throw IME.LayoutNotFound(App.getConfigString("basekeyboardlayout")), lowLayerCodes.linearSearch { it == App.getConfigString("basekeyboardlayout") } ?: throw IME.LayoutNotFound(App.getConfigString("basekeyboardlayout")),
textSelWidth textSelWidth
@@ -127,8 +127,8 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
private val imeCodes = listOf("none") + imeCodes0 private val imeCodes = listOf("none") + imeCodes0
private val imeNames = listOf({"$EMDASH"}) + imeCodes0.map { { IME.getHighLayerByName(it).name } } private val imeNames = listOf({"$EMDASH"}) + imeCodes0.map { { IME.getHighLayerByName(it).name } }
private val imeSelection = UIItemTextSelector(this, private val imeSelection = UIItemTextSelector(this,
selDrawX + halfselw + (halfselw - textSelWidth) / 2, selDrawX + halfselw + (halfselw - textSelWidth) / 2,
y2, kby + 260,
imeNames, imeNames,
imeCodes.linearSearch { it == App.getConfigString("inputmethod") } ?: throw IME.LayoutNotFound(App.getConfigString("inputmethod")), imeCodes.linearSearch { it == App.getConfigString("inputmethod") } ?: throw IME.LayoutNotFound(App.getConfigString("inputmethod")),
textSelWidth textSelWidth
@@ -138,7 +138,7 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
private val keyboardTestPanel = UIItemTextLineInput(this, private val keyboardTestPanel = UIItemTextLineInput(this,
drawX + (width - 480) / 2 + 3, drawX + (width - 480) / 2 + 3,
height - 40, drawY + height - 120,
474 474
) )
@@ -171,23 +171,23 @@ class UIIMEConfig(remoCon: UIRemoCon?) : UICanvas() {
batch.color = Color.WHITE batch.color = Color.WHITE
val txt1 = Lang["MENU_LABEL_KEYBOARD_LAYOUT"]; val tw1 = App.fontGame.getWidth(txt1) 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) 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 // title
// todo show "Keyboard"/"Gamepad" accordingly // todo show "Keyboard"/"Gamepad" accordingly
val title = Lang["MENU_CONTROLS_KEYBOARD"] val title = Lang["MENU_CONTROLS_KEYBOARD"]
batch.color = Color.WHITE 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 // button help for string input UI
val help1 = "${Lang["MENU_LABEL_IME_TOGGLE"]}" 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"]}" 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 defaultSelection = null
) )
private val areYouSureMainMenuButtons = UIItemTextButtonList( 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, (width - gameMenuListWidth) / 2,
INVENTORY_CELLS_OFFSET_Y() + (INVENTORY_CELLS_UI_HEIGHT - (DEFAULT_LINE_HEIGHT * 3)) / 2, INVENTORY_CELLS_OFFSET_Y() + (INVENTORY_CELLS_UI_HEIGHT - (DEFAULT_LINE_HEIGHT * 3)) / 2,
gameMenuListWidth, DEFAULT_LINE_HEIGHT * 3, gameMenuListWidth, DEFAULT_LINE_HEIGHT * 3,
@@ -60,7 +60,12 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
highlightBackCol = Color(0), highlightBackCol = Color(0),
inactiveCol = Color.WHITE, inactiveCol = Color.WHITE,
defaultSelection = null defaultSelection = null
) ).also {
listOf(it.buttons[0], it.buttons[1]).forEach {
it.skipUpdate = true
it.isActive = false
}
}
/*private val areYouSureQuitButtons = UIItemTextButtonList( /*private val areYouSureQuitButtons = UIItemTextButtonList(
this, DEFAULT_LINE_HEIGHT, arrayOf("MENU_LABEL_DESKTOP_QUESTION", "MENU_LABEL_DESKTOP", "MENU_LABEL_CANCEL"), this, DEFAULT_LINE_HEIGHT, arrayOf("MENU_LABEL_DESKTOP_QUESTION", "MENU_LABEL_DESKTOP", "MENU_LABEL_CANCEL"),
(width - gameMenuListWidth) / 2, (width - gameMenuListWidth) / 2,
@@ -105,11 +110,19 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
full.unlockTransition() 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 playerSavefile = getPlayerSaveFiledesc(INGAME.playerSavefileName)
val worldSavefile = getWorldSaveFiledesc(INGAME.worldSavefileName) val worldSavefile = getWorldSaveFiledesc(INGAME.worldSavefileName)
INGAME.makeSavegameBackupCopy(playerSavefile) INGAME.makeSavegameBackupCopy(playerSavefile)
WriteSavegame(saveTime_t, WriteSavegame.SaveMode.PLAYER, INGAME.playerDisk, playerSavefile, INGAME as TerrarumIngame, false, onError) { 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 // return to normal state
System.gc() onSuccessful()
screen = 0
full.handler.unlockToggle()
full.unlockTransition()
(INGAME as TerrarumIngame).autosaveTimer = 0f
} }
} }*/
INGAME.saveTheGame(onSuccessful, onError)
} }
1 -> { 1 -> {
@@ -148,11 +160,11 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
} }
areYouSureMainMenuButtons.selectionChangeListener = { _, new -> areYouSureMainMenuButtons.selectionChangeListener = { _, new ->
when (new) { when (new) {
1 -> { 2 -> {
areYouSureMainMenuButtons.deselect() areYouSureMainMenuButtons.deselect()
App.setScreen(TitleScreen(App.batch)) App.setScreen(TitleScreen(App.batch))
} }
2 -> { 3 -> {
screen = 0; areYouSureMainMenuButtons.deselect() 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.Toolkit
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItemHorizontalFadeSlide import net.torvald.terrarum.ui.UIItemHorizontalFadeSlide
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import net.torvald.unicode.* import net.torvald.unicode.*
/** /**
@@ -31,8 +32,20 @@ class UIInventoryFull(
override var height: Int = App.scr.height override var height: Int = App.scr.height
companion object { 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 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_X = { 0 + (Toolkit.drawWidth - internalWidth) / 2 }
val INVENTORY_CELLS_OFFSET_Y = { -YPOS_CORRECTION + 107 + (App.scr.height - internalHeight) / 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 catBarWidth = 330
val gradStartCol = Color(0x404040_60) val gradStartCol = Color(0x404040_60)
@@ -69,9 +85,11 @@ class UIInventoryFull(
private val gsta = Color(gradStartCol) private val gsta = Color(gradStartCol)
private val gend = Color(gradEndCol) private val gend = Color(gradEndCol)
private val drawBackgroundColourBuffer = Color(1f,1f,1f,1f)
fun drawBackground(batch: SpriteBatch, opacity: Float) { fun drawBackground(batch: SpriteBatch, opacity: Float) {
batch.end()
gdxBlendNormalStraightAlpha() gdxBlendNormalStraightAlpha()
/*batch.end()
if (shapeRenderer == null) { if (shapeRenderer == null) {
shapeRenderer = App.makeShapeRenderer() shapeRenderer = App.makeShapeRenderer()
@@ -101,7 +119,23 @@ class UIInventoryFull(
it.rect(0f, h, w, -(h - gradBottomEnd), gsta, gsta, gsta, gsta) 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) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
drawBackground(batch, handler.opacity) drawBackground(batch, 1f)
// UI items // UI items
catBar.render(batch, camera) catBar.render(batch, camera)

View File

@@ -86,13 +86,18 @@ open class UIItemInventoryItemGrid(
private val inventoryUI = parentUI private val inventoryUI = parentUI
var itemPage = 0 var itemPage
set(value) { set(value) {
field = if (itemPageCount == 0) 0 else (value).fmod(itemPageCount) navRemoCon.itemPage = if (itemPageCount == 0) 0 else (value).fmod(itemPageCount)
rebuild(currentFilter) rebuild(currentFilter)
} }
var itemPageCount = 1 // TODO total size of current category / items.size get() = navRemoCon.itemPage
protected set
var itemPageCount // TODO total size of current category / items.size
protected set(value) {
navRemoCon.itemPageCount = value
}
get() = navRemoCon.itemPageCount
var inventorySortList = ArrayList<InventoryPair>() var inventorySortList = ArrayList<InventoryPair>()
protected var rebuildList = true protected var rebuildList = true
@@ -140,35 +145,37 @@ open class UIItemInventoryItemGrid(
fun createInvCellGenericTouchDownFun(listRebuildFun: () -> Unit): (GameItem?, Long, Int, Any?, UIItemInventoryCellBase) -> Unit { fun createInvCellGenericTouchDownFun(listRebuildFun: () -> Unit): (GameItem?, Long, Int, Any?, UIItemInventoryCellBase) -> Unit {
return { item: GameItem?, amount: Long, button: Int, _, _ -> return { item: GameItem?, amount: Long, button: Int, _, _ ->
if (item != null && Terrarum.ingame != null) { if (button == App.getConfigInt("config_mouseprimary")) {
// equip da shit if (item != null && Terrarum.ingame != null) {
val itemEquipSlot = item.equipPosition // equip da shit
if (itemEquipSlot == GameItem.EquipPosition.NULL) { val itemEquipSlot = item.equipPosition
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})") 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)
}
} }
else { // if not, unequip it val player = (Terrarum.ingame!! as TerrarumIngame).actorNowPlaying
player.unequipItem(item) if (player != null) {
// also unequip on the quickslot if (item != ItemCodex[player.inventory.itemEquipped.get(itemEquipSlot)]) { // if this item is unequipped, equip it
player.actorValue.getAsInt(AVKey.__PLAYER_QUICKSLOTSEL)?.let { player.equipItem(item)
player.inventory.setQuickslotItem(it, null)
// 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) private val iconPosX = if (drawScrollOnRightside)
posX + width + LIST_TO_CONTROL_GAP posX + width
else else
posX - LIST_TO_CONTROL_GAP - catBar.catIcons.tileW posX - UIItemListNavBarVertical.LIST_TO_CONTROL_GAP - UIItemListNavBarVertical.WIDTH - 4
/** 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
)
fun setCustomHighlightRuleMain(predicate: ((UIItemInventoryCellBase) -> Boolean)?) { fun setCustomHighlightRuleMain(predicate: ((UIItemInventoryCellBase) -> Boolean)?) {
itemGrid.forEach { it.customHighlightRuleMain = predicate } itemGrid.forEach { it.customHighlightRuleMain = predicate }
@@ -282,63 +248,53 @@ open class UIItemInventoryItemGrid(
itemPage = if (itemPageCount == 0) 0 else (itemPage + relativeAmount).fmod(itemPageCount) 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 { init {
// initially highlight grid mode buttons // initially highlight grid mode buttons
if (!hideSidebar) { if (!hideSidebar) {
gridModeButtons[if (isCompactMode) 1 else 0].highlighted = true navRemoCon.listButtonListener = { _, _ ->
gridModeButtons[0].touchDownListener = { _, _, _, _ ->
isCompactMode = false isCompactMode = false
gridModeButtons[0].highlighted = true
gridModeButtons[1].highlighted = false
itemPage = 0
rebuild(currentFilter) rebuild(currentFilter)
} }
gridModeButtons[1].touchDownListener = { _, _, _, _ -> navRemoCon.gridButtonListener = { _, _ ->
isCompactMode = true isCompactMode = true
gridModeButtons[0].highlighted = false
gridModeButtons[1].highlighted = true
itemPage = 0
rebuild(currentFilter) rebuild(currentFilter)
} }
navRemoCon.scrollUpListener = { _, it ->
scrollUpButton.clickOnceListener = { _, _, _ -> it.highlighted = false
scrollUpButton.highlighted = false
scrollItemPage(-1) scrollItemPage(-1)
} }
scrollDownButton.clickOnceListener = { _, _, _ -> navRemoCon.scrollDownListener = { _, it ->
scrollDownButton.highlighted = false it.highlighted = false
scrollItemPage(1) scrollItemPage(1)
} }
// draw wallet text
// if (is.mouseUp) handled by this.touchDown() 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) = // private fun getIconPosY(index: Int) =
posY + 8 + 26 * index // posY + 8 + 26 * index
override fun render(batch: SpriteBatch, camera: Camera) { override fun render(batch: SpriteBatch, camera: Camera) {
val posXDelta = posX - oldPosX val posXDelta = posX - oldPosX
itemGrid.forEach { it.posX += posXDelta } itemGrid.forEach { it.posX += posXDelta }
itemList.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 // define each button's highlighted status from the list of forceHighlighted, then render the button
items.forEach { items.forEach {
if (useHighlightingManager) it.forceHighlighted = forceHighlightList.contains(it.item?.dynamicID) if (useHighlightingManager) it.forceHighlighted = forceHighlightList.contains(it.item?.dynamicID)
@@ -346,41 +302,7 @@ open class UIItemInventoryItemGrid(
} }
if (!hideSidebar) { if (!hideSidebar) {
// draw the tray navRemoCon.render(batch, camera)
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
)
}
} }
super.render(batch, camera) super.render(batch, camera)
@@ -424,9 +346,7 @@ open class UIItemInventoryItemGrid(
if (!hideSidebar) { if (!hideSidebar) {
gridModeButtons.forEach { it.update(delta) } navRemoCon.update(delta)
scrollUpButton.update(delta)
scrollDownButton.update(delta)
} }
} }
@@ -549,9 +469,7 @@ open class UIItemInventoryItemGrid(
items.forEach { if (it.mouseUp) it.touchDown(screenX, screenY, pointer, button) } items.forEach { if (it.mouseUp) it.touchDown(screenX, screenY, pointer, button) }
if (!hideSidebar) { if (!hideSidebar) {
gridModeButtons.forEach { if (it.mouseUp) it.touchDown(screenX, screenY, pointer, button) } navRemoCon.touchDown(screenX, screenY, pointer, button)
if (scrollUpButton.mouseUp) scrollUpButton.touchDown(screenX, screenY, pointer, button)
if (scrollDownButton.mouseUp) scrollDownButton.touchDown(screenX, screenY, pointer, button)
} }
return true 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.CommonResourcePool
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.serialise.WriteSavegame import net.torvald.terrarum.modulebasegame.serialise.WriteSavegame
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItem import net.torvald.terrarum.ui.UIItem
import kotlin.math.roundToInt 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) { override fun render(batch: SpriteBatch, camera: Camera) {
// these things will not scroll along with the parent GUI!
val t = Lang["MENU_IO_SAVING"] val t = Lang["MENU_IO_SAVING"]
val tlen = App.fontGame.getWidth(t) 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 // -1..63
val index = ((WriteSavegame.saveProgress / WriteSavegame.saveProgressMax) * circles).roundToInt() - 1 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 val sy = index / circleSheet.horizontalCount
// q&d fix for ArrayIndexOutOfBoundsException caused when saving huge world... wut? // q&d fix for ArrayIndexOutOfBoundsException caused when saving huge world... wut?
if (sx in 0 until circleSheet.horizontalCount && sy in 0 until circleSheet.horizontalCount) { 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) } keycaps.values.forEach { addUIitem(it) }
updateKeycaps() updateKeycaps()
buttonReset.clickOnceListener = { x, y, button -> buttonReset.clickOnceListener = { x, y ->
resetKeyConfig() resetKeyConfig()
updateKeycaps() updateKeycaps()
} }
@@ -201,7 +201,9 @@ class UIKeyboardControlPanel(remoCon: UIRemoCon?) : UICanvas() {
// todo show "Keyboard"/"Gamepad" accordingly // todo show "Keyboard"/"Gamepad" accordingly
batch.color = Color.WHITE batch.color = Color.WHITE
val title = Lang["MENU_CONTROLS_KEYBOARD"] 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"] val desc = Lang["MENU_LABEL_KEYCONFIG_HELP1"]
App.fontGame.draw(batch, desc, drawX.toFloat() + (width - App.fontGame.getWidth(desc)) / 2, drawY + 360f) 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.Toolkit
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.ui.UIItem 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 net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import java.time.Instant import java.time.Instant
import java.time.format.DateTimeFormatter 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! * WARNING: the values are not guaranteed to reset when the selector UI is closed!
*/ */
object UILoadGovernor { 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 var playerDisk: DiskSkimmer? = null
set(value) { set(value) {
printdbg(this, "Player selected: ${value?.diskFile?.name}") printdbg(this, "Player selected: ${value?.diskFile?.name}")
@@ -71,15 +78,23 @@ object UILoadGovernor {
printdbg(this, "Resetting player and world selection") printdbg(this, "Resetting player and world selection")
playerDisk = null playerDisk = null
worldDisk = 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] * Only works if current screen set by the App is [TitleScreen]
* *
* Created by minjaesong on 2021-09-09. * Created by minjaesong on 2021-09-09.
*/ */
class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() { class UILoadDemoSavefiles(val remoCon: UIRemoCon) : Advanceable() {
// private val hash = RandomWordsName(3) // private val hash = RandomWordsName(3)
@@ -155,7 +170,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
this.mode = mode this.mode = mode
} }
fun advanceMode() { override fun advanceMode(button: UIItem) {
mode += 1 mode += 1
uiScroll = 0f uiScroll = 0f
scrollFrom = 0 scrollFrom = 0
@@ -175,7 +190,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
// read savegames // read savegames
var savegamesCount = 0 var savegamesCount = 0
App.sortedSavegameWorlds.forEach { uuid -> App.sortedSavegameWorlds.forEach { uuid ->
val skimmer = App.savegameWorlds[uuid]!! val skimmer = App.savegameWorlds[uuid]!!.loadable()
val x = uiX val x = uiX
val y = titleTopGradEnd + cellInterval * savegamesCount val y = titleTopGradEnd + cellInterval * savegamesCount
try { try {
@@ -190,7 +205,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
savegamesCount = 0 savegamesCount = 0
App.sortedPlayers.forEach { uuid -> App.sortedPlayers.forEach { uuid ->
val skimmer = App.savegamePlayers[uuid]!! val skimmer = App.savegamePlayers[uuid]!!.loadable()
val x = uiX val x = uiX
val y = titleTopGradEnd + cellInterval * savegamesCount val y = titleTopGradEnd + cellInterval * savegamesCount
try { try {
@@ -374,7 +389,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
// draw texts // draw texts
val loadGameTitleStr = Lang[titles[mode]]// + "$EMDASH$hash" val loadGameTitleStr = Lang[titles[mode]]// + "$EMDASH$hash"
// "Game Load" // "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 // Control help
App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat()) App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat())
} }
@@ -470,7 +485,7 @@ class UILoadDemoSavefiles(val remoCon: UIRemoCon) : UICanvas() {
class UIItemPlayerCells( class UIItemPlayerCells(
parent: UILoadDemoSavefiles, parent: Advanceable,
initialX: Int, initialX: Int,
initialY: Int, initialY: Int,
val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) { val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) {
@@ -478,9 +493,11 @@ class UIItemPlayerCells(
override val width = SAVE_CELL_WIDTH override val width = SAVE_CELL_WIDTH
override val height = SAVE_CELL_HEIGHT 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 UILoadGovernor.playerDisk = skimmer
parent.advanceMode() UILoadGovernor.playerUUID = playerUUID
UILoadGovernor.worldUUID = worldUUID
parent.advanceMode(this)
} }
private var playerName: String = "$EMDASH" private var playerName: String = "$EMDASH"
@@ -488,28 +505,25 @@ class UIItemPlayerCells(
private var lastPlayTime: String = "????-??-?? --:--:--" private var lastPlayTime: String = "????-??-?? --:--:--"
private var totalPlayTime: String = "--h--m--s" 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 { init {
skimmer.getFile(SAVEGAMEINFO)?.bytes?.let { skimmer.getFile(SAVEGAMEINFO)?.bytes?.let {
val json = JsonReader().parse(ByteArray64Reader(it, Common.CHARSET)) var lastPlayTime0 = 0L
playerUUID = UUID.fromString(json["uuid"]?.asString()) JsonFetcher.readFromJsonString(ByteArray64Reader(it, Common.CHARSET)).forEachSiblings { name, value ->
val worldUUID = UUID.fromString(json["worldCurrentlyPlaying"]?.asString()) 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.savegamePlayersName[playerUUID]?.let { if (it.isNotBlank()) playerName = it else "(name)" }
App.savegameWorldsName[worldUUID]?.let { if (it.isNotBlank()) worldName = it } App.savegameWorldsName[worldUUID]?.let { if (it.isNotBlank()) worldName = it }
/*json["lastPlayTime"]?.asString()?.let { lastPlayTime = Instant.ofEpochSecond(lastPlayTime0)
lastPlayTime = Instant.ofEpochSecond(it.toLong())
.atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}*/
lastPlayTime = Instant.ofEpochSecond(skimmer.getLastModifiedTime())
.atZone(TimeZone.getDefault().toZoneId()) .atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) .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 highlightCol: Color = defaultCol
private var highlightTextCol: Color = defaultCol private var highlightTextCol: Color = defaultCol
var forceMouseDown = false
override fun update(delta: Float) { override fun update(delta: Float) {
super.update(delta) super.update(delta)
highlightCol = if (mouseUp) litCol else defaultCol highlightCol = if (mouseUp && !forceMouseDown) litCol else defaultCol
highlightTextCol = if (mouseUp) litCol else Toolkit.Theme.COL_LIST_DEFAULT highlightTextCol = if (mouseUp && !forceMouseDown) litCol else Toolkit.Theme.COL_LIST_DEFAULT
} }
override fun render(batch: SpriteBatch, camera: Camera) { override fun render(batch: SpriteBatch, camera: Camera) {
@@ -670,7 +686,7 @@ class UIItemPlayerCells(
class UIItemWorldCells( class UIItemWorldCells(
parent: UILoadDemoSavefiles, parent: Advanceable,
initialX: Int, initialX: Int,
initialY: Int, initialY: Int,
val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) { val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) {
@@ -685,9 +701,6 @@ class UIItemWorldCells(
private val lastPlayedTimestamp: String private val lastPlayedTimestamp: String
init { init {
printdbg(this, "Rebuilding skimmer for savefile ${skimmer.diskFile.absolutePath}")
skimmer.rebuild()
metaFile = skimmer.getFile(-1) metaFile = skimmer.getFile(-1)
if (metaFile == null) saveDamaged = true if (metaFile == null) saveDamaged = true
@@ -699,14 +712,18 @@ class UIItemWorldCells(
saveDamaged = saveDamaged or checkForSavegameDamage(skimmer) saveDamaged = saveDamaged or checkForSavegameDamage(skimmer)
if (metaFile != null) { if (metaFile != null) {
val worldJson = JsonReader().parse(ByteArray64Reader(metaFile.bytes, Common.CHARSET)) // val lastplay_t = skimmer.getLastModifiedTime()//worldJson["lastPlayTime"].asLong()
val lastplay_t = skimmer.getLastModifiedTime()//worldJson["lastPlayTime"].asLong() var playtime_t = ""
val playtime_t = worldJson["totalPlayTime"].asLong() 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 = lastPlayedTimestamp =
Instant.ofEpochSecond(lastplay_t) Instant.ofEpochSecond(lastplay_t)
.atZone(TimeZone.getDefault().toZoneId()) .atZone(TimeZone.getDefault().toZoneId())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +
"/${parseDuration(playtime_t)}" "/$playtime_t"
} }
else { else {
lastPlayedTimestamp = "--:--:--/--h--m--s" lastPlayedTimestamp = "--:--:--/--h--m--s"
@@ -739,9 +756,9 @@ class UIItemWorldCells(
private var highlightCol: Color = Toolkit.Theme.COL_LIST_DEFAULT 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 UILoadGovernor.worldDisk = skimmer
parent.advanceMode() parent.advanceMode(this)
} }
internal var hasTexture = false 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.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.Second import net.torvald.terrarum.Second
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.gameactors.AVKey
@@ -47,7 +48,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
private var uiLocked = false private var uiLocked = false
init { init {
goButton.touchDownListener = { _, _, _, _ -> goButton.clickOnceListener = { _,_ ->
uiLocked = true uiLocked = true
@@ -64,6 +65,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
val savingThread = Thread({ val savingThread = Thread({
printdbg(this, "Player saving thread fired")
disk.saveMode = 2 // auto, no quick disk.saveMode = 2 // auto, no quick
disk.capacity = 0L disk.capacity = 0L
@@ -79,12 +81,17 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
UILoadGovernor.playerDisk = DiskSkimmer(outFile) UILoadGovernor.playerDisk = DiskSkimmer(outFile)
// comment above if chargen must send gamers back to the charcters list // comment above if chargen must send gamers back to the charcters list
printdbg(this, "playerdisk: ${UILoadGovernor.playerDisk?.diskFile?.path}")
}, "TerrarumBasegameNewCharcterSaveThread") }, "TerrarumBasegameNewCharcterSaveThread")
savingThread.start()
// savingThread.start()
// savingThread.join()
remoCon.openUI(UINewWorld(remoCon, savingThread)) // let UINewWorld handle the character file generation
} }
backButton.touchDownListener = { _, _, _, _ -> backButton.clickOnceListener = { _,_ ->
remoCon.openUI(UILoadDemoSavefiles(remoCon, 0)) remoCon.openUI(UILoadSavegame(remoCon))
} }
addUIitem(nameInput) addUIitem(nameInput)
@@ -99,7 +106,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
if (returnedFromChargen) { if (returnedFromChargen) {
returnedFromChargen = false 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 batch.color = Color.WHITE
// ui title // ui title
// val titlestr = Lang["CONTEXT_WORLD_NEW"] // 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 // 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 com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.random.XXHash64 import net.torvald.random.XXHash64
import net.torvald.terrarum.App import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg 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.langpack.Lang
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.TerrarumIngame.Companion.NEW_WORLD_SIZE 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() { 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 hugeTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/huge.png")))
private val largeTex = TextureRegion(Texture(ModMgr.getGdxFile("basegame", "gui/large.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 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 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) 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 { init {
goButton.touchDownListener = { _, _, _, _ -> goButton.clickOnceListener = { _, _ ->
// printdbg(this, "generate! Size=${sizeSelector.selection}, Name=${nameInput.getTextOrPlaceholder()}, Seed=${seedInput.getTextOrPlaceholder()}")
// 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 ingame = TerrarumIngame(App.batch)
val player = ReadActor.invoke(UILoadGovernor.playerDisk!!, ByteArray64Reader(UILoadGovernor.playerDisk!!.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET)) as IngamePlayer 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) App.setLoadScreen(loadScreen)
} }
backButton.touchDownListener = { _, _, _, _ -> backButton.clickOnceListener = { _, _ ->
remoCon.openUI(UILoadDemoSavefiles(remoCon, 1)) remoCon.openUI(UILoadSavegame(remoCon))
} }
addUIitem(sizeSelector) addUIitem(sizeSelector)
@@ -131,7 +141,7 @@ class UINewWorld(val remoCon: UIRemoCon) : UICanvas() {
batch.color = Color.WHITE batch.color = Color.WHITE
// ui title // ui title
// val titlestr = Lang["CONTEXT_WORLD_NEW"] // 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 // draw size previews
val texture = tex[sizeSelector.selection.coerceAtMost(tex.lastIndex)] 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 // update controls
if (handler.isOpened || handler.isOpening) { if (handler.isOpened || handler.isOpening) {
val cursorPos = Vector2(Terrarum.mouseScreenX.toDouble(), Terrarum.mouseScreenY.toDouble()) 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() val deg = -(centre - cursorPos).direction.toFloat()
selection = Math.round(deg * slotCount / FastMath.TWO_PI) 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.gameitems.GameItem
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory 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.Toolkit
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import net.torvald.unicode.getKeycapPC import net.torvald.unicode.getKeycapPC
@@ -23,7 +24,7 @@ internal class UIStorageChest : UICanvas(
lateinit var chestInventory: FixtureInventory lateinit var chestInventory: FixtureInventory
lateinit var chestNameFun: () -> String lateinit var chestNameFun: () -> String
override var width = App.scr.width override var width = Toolkit.drawWidth
override var height = App.scr.height override var height = App.scr.height
private val negotiator = object : InventoryTransactionNegotiator() { private val negotiator = object : InventoryTransactionNegotiator() {
@@ -49,12 +50,12 @@ internal class UIStorageChest : UICanvas(
private var encumbrancePerc = 0f private var encumbrancePerc = 0f
private var isEncumbered = false private var isEncumbered = false
private var halfSlotOffset = (UIItemInventoryElemSimple.height + UIItemInventoryItemGrid.listGap) / 2 private var halfSlotOffset = (UIItemInventoryElemSimple.height + UIItemInventoryItemGrid.listGap * 2) / 2
init { init {
catBar = UIItemInventoryCatBar( catBar = UIItemInventoryCatBar(
this, this,
(App.scr.width - UIInventoryFull.catBarWidth) / 2, (width - UIInventoryFull.catBarWidth) / 2,
42 - UIInventoryFull.YPOS_CORRECTION + (App.scr.height - UIInventoryFull.internalHeight) / 2, 42 - UIInventoryFull.YPOS_CORRECTION + (App.scr.height - UIInventoryFull.internalHeight) / 2,
UIInventoryFull.internalWidth, UIInventoryFull.internalWidth,
UIInventoryFull.catBarWidth, UIInventoryFull.catBarWidth,
@@ -65,42 +66,46 @@ internal class UIStorageChest : UICanvas(
this, this,
catBar, catBar,
{ getFixtureInventory() }, { getFixtureInventory() },
UIInventoryFull.INVENTORY_CELLS_OFFSET_X() - halfSlotOffset, Toolkit.hdrawWidth - getWidthOfCells(6) - halfSlotOffset,
UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(), UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(),
6, UIInventoryFull.CELLS_VRT, 6, UIInventoryFull.CELLS_VRT,
drawScrollOnRightside = false, drawScrollOnRightside = false,
drawWallet = false, drawWallet = false,
keyDownFun = { _, _, _, _, _ -> Unit }, keyDownFun = { _, _, _, _, _ -> Unit },
touchDownFun = { gameItem, amount, _, _, _ -> touchDownFun = { gameItem, amount, button, _, _ ->
if (gameItem != null) { if (button == App.getConfigInt("config_mouseprimary")) {
negotiator.reject(getFixtureInventory(), getPlayerInventory(), gameItem, amount) if (gameItem != null) {
negotiator.reject(getFixtureInventory(), getPlayerInventory(), gameItem, amount)
}
itemListUpdate()
} }
itemListUpdate()
} }
) )
// make grid mode buttons work together // make grid mode buttons work together
itemListChest.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) } itemListChest.navRemoCon.listButtonListener = { _,_ -> setCompact(false) }
itemListChest.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) } itemListChest.navRemoCon.gridButtonListener = { _,_ -> setCompact(true) }
itemListPlayer = UIItemInventoryItemGrid( itemListPlayer = UIItemInventoryItemGrid(
this, this,
catBar, catBar,
{ INGAME.actorNowPlaying!!.inventory }, // literally a player's inventory { 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(), UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(),
6, UIInventoryFull.CELLS_VRT, 6, UIInventoryFull.CELLS_VRT,
drawScrollOnRightside = true, drawScrollOnRightside = true,
drawWallet = false, drawWallet = false,
keyDownFun = { _, _, _, _, _ -> Unit }, keyDownFun = { _, _, _, _, _ -> Unit },
touchDownFun = { gameItem, amount, _, _, _ -> touchDownFun = { gameItem, amount, button, _, _ ->
if (gameItem != null) { if (button == App.getConfigInt("config_mouseprimary")) {
negotiator.accept(getPlayerInventory(), getFixtureInventory(), gameItem, amount) if (gameItem != null) {
negotiator.accept(getPlayerInventory(), getFixtureInventory(), gameItem, amount)
}
itemListUpdate()
} }
itemListUpdate()
} }
) )
itemListPlayer.gridModeButtons[0].touchDownListener = { _,_,_,_ -> setCompact(false) } itemListPlayer.navRemoCon.listButtonListener = { _,_ -> setCompact(false) }
itemListPlayer.gridModeButtons[1].touchDownListener = { _,_,_,_ -> setCompact(true) } itemListPlayer.navRemoCon.gridButtonListener = { _,_ -> setCompact(true) }
handler.allowESCtoClose = true handler.allowESCtoClose = true
@@ -132,14 +137,14 @@ internal class UIStorageChest : UICanvas(
private fun setCompact(yes: Boolean) { private fun setCompact(yes: Boolean) {
itemListChest.isCompactMode = yes itemListChest.isCompactMode = yes
itemListChest.gridModeButtons[0].highlighted = !yes itemListChest.navRemoCon.gridModeButtons[0].highlighted = !yes
itemListChest.gridModeButtons[1].highlighted = yes itemListChest.navRemoCon.gridModeButtons[1].highlighted = yes
itemListChest.itemPage = 0 itemListChest.itemPage = 0
itemListChest.rebuild(catBar.catIconsMeaning[catBar.selectedIcon]) itemListChest.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
itemListPlayer.isCompactMode = yes itemListPlayer.isCompactMode = yes
itemListPlayer.gridModeButtons[0].highlighted = !yes itemListPlayer.navRemoCon.gridModeButtons[0].highlighted = !yes
itemListPlayer.gridModeButtons[1].highlighted = yes itemListPlayer.navRemoCon.gridModeButtons[1].highlighted = yes
itemListPlayer.itemPage = 0 itemListPlayer.itemPage = 0
itemListPlayer.rebuild(catBar.catIconsMeaning[catBar.selectedIcon]) itemListPlayer.rebuild(catBar.catIconsMeaning[catBar.selectedIcon])
@@ -161,7 +166,7 @@ internal class UIStorageChest : UICanvas(
if (openingClickLatched && !Terrarum.mouseDown) openingClickLatched = false 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 thisOffsetX2 = thisOffsetX + (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 7
private val thisOffsetY = UIInventoryFull.INVENTORY_CELLS_OFFSET_Y() private val thisOffsetY = UIInventoryFull.INVENTORY_CELLS_OFFSET_Y()
private val cellsWidth = (UIItemInventoryItemGrid.listGap + UIItemInventoryElemWide.height) * 6 - UIItemInventoryItemGrid.listGap 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) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
// background fill // background fill
UIInventoryFull.drawBackground(batch, handler.opacity) UIInventoryFull.drawBackground(batch, 1f)
// UI items // UI items
batch.color = Color.WHITE batch.color = Color.WHITE
@@ -232,19 +237,23 @@ internal class UIStorageChest : UICanvas(
} }
override fun doOpening(delta: Float) { override fun doOpening(delta: Float) {
super.doOpening(delta)
INGAME.pause() INGAME.pause()
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
} }
override fun doClosing(delta: Float) { override fun doClosing(delta: Float) {
super.doClosing(delta)
INGAME.resume() INGAME.resume()
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
} }
override fun endOpening(delta: Float) { override fun endOpening(delta: Float) {
super.endOpening(delta)
} }
override fun endClosing(delta: Float) { override fun endClosing(delta: Float) {
super.endClosing(delta)
UIItemInventoryItemGrid.tooltipShowing.clear() UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required! INGAME.setTooltipMessage(null) // required!
} }

View File

@@ -88,7 +88,7 @@ class UITitleModules(val remoCon: UIRemoCon) : UICanvas() {
savegamesCount += 1 savegamesCount += 1
} }
catch (e: Throwable) { 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() e.printStackTrace()
} }
} }
@@ -192,7 +192,7 @@ class UITitleModules(val remoCon: UIRemoCon) : UICanvas() {
// draw texts // draw texts
val loadGameTitleStr = Lang["MENU_MODULES"]// + "$EMDASH$hash" val loadGameTitleStr = Lang["MENU_MODULES"]// + "$EMDASH$hash"
// "Game Load" // "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 // Control help
App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat()) App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat())
} }

View File

@@ -12,12 +12,13 @@ object UITitleRemoConYaml {
* The class must be the UICanvas * The class must be the UICanvas
*/ */
val menuBase = """ val menuBase = """
- MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadDemoSavefiles - MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame
- MENU_OPTIONS - MENU_OPTIONS
- MENU_LABEL_GRAPHICS : net.torvald.terrarum.modulebasegame.ui.UIGraphicsControlPanel - MENU_LABEL_GRAPHICS : net.torvald.terrarum.modulebasegame.ui.UIGraphicsControlPanel
- MENU_OPTIONS_CONTROLS : net.torvald.terrarum.modulebasegame.ui.UIKeyboardControlPanel - MENU_OPTIONS_CONTROLS : net.torvald.terrarum.modulebasegame.ui.UIKeyboardControlPanel
- MENU_LABEL_IME : net.torvald.terrarum.modulebasegame.ui.UIIMEConfig - MENU_LABEL_IME : net.torvald.terrarum.modulebasegame.ui.UIIMEConfig
- MENU_LABEL_LANGUAGE : net.torvald.terrarum.modulebasegame.ui.UITitleLanguage - 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_MODULES : net.torvald.terrarum.ModOptionsHost
- MENU_LABEL_RETURN+WRITETOCONFIG - MENU_LABEL_RETURN+WRITETOCONFIG
- MENU_MODULES : net.torvald.terrarum.modulebasegame.ui.UITitleModules - MENU_MODULES : net.torvald.terrarum.modulebasegame.ui.UITitleModules
@@ -25,6 +26,7 @@ object UITitleRemoConYaml {
- MENU_LABEL_CREDITS - MENU_LABEL_CREDITS
- MENU_LABEL_COPYRIGHT : net.torvald.terrarum.modulebasegame.ui.UITitleCredits - MENU_LABEL_COPYRIGHT : net.torvald.terrarum.modulebasegame.ui.UITitleCredits
- MENU_CREDIT_GPL_DNT : net.torvald.terrarum.modulebasegame.ui.UITitleGPL3 - 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_RETURN
- MENU_LABEL_QUIT - 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 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 batch.color = tooltipBackCol
Toolkit.drawBaloon(batch, Toolkit.drawBaloon(batch,
tooltipX - textMarginX, tooltipX - textMarginX,
tooltipY, tooltipY,
tooltipW, tooltipW,
font.lineHeight * msgBuffer.size font.lineHeight * msgBuffer.size,
Notification.OPACITY
) )
batch.color = tooltipForeCol 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.Camera
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.jme3.math.FastMath
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent
import net.torvald.terrarum.langpack.Lang 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.INVENTORY_CELLS_OFFSET_Y
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.YPOS_CORRECTION 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.drawBackground
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.internalHeight import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.internalHeight
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.internalWidth import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.internalWidth
import net.torvald.terrarum.serialise.toAscii85
import net.torvald.terrarum.ui.* import net.torvald.terrarum.ui.*
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import net.torvald.unicode.getKeycapConsole
import net.torvald.unicode.getKeycapPC import net.torvald.unicode.getKeycapPC
import java.util.UUID
/** /**
* Structure: * Structure:
@@ -34,60 +38,45 @@ class UIWorldPortal : UICanvas(
override var width: Int = Toolkit.drawWidth override var width: Int = Toolkit.drawWidth
override var height: Int = App.scr.height override var height: Int = App.scr.height
internal lateinit var host: FixtureWorldPortal
val controlHelpHeight = App.fontGame.lineHeight 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 " private val SP = "\u3000 "
val portalListingControlHelp: String val portalListingControlHelp: String
get() = if (App.environment == RunningEnvironment.PC) get() = if (App.environment == RunningEnvironment.PC)
"${getKeycapPC(App.getConfigInt("control_key_up"))}${getKeycapPC(App.getConfigInt("control_key_down"))}" + "${getKeycapPC(App.getConfigInt("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}"
" ${Lang["MENU_CONTROLS_SCROLL"]}" +
"$SP${getKeycapPC(App.getConfigInt("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}"
else else
"${getKeycapConsole('R')} ${Lang["MENU_CONTROLS_SCROLL"]}" + "${App.gamepadLabelStart} ${Lang["GAME_ACTION_CLOSE"]}" +
"$SP${App.gamepadLabelStart} ${Lang["GAME_ACTION_CLOSE"]}" +
"$SP${App.gamepadLabelLT} ${Lang["GAME_WORLD_SEARCH"]}" + "$SP${App.gamepadLabelLT} ${Lang["GAME_WORLD_SEARCH"]}" +
"$SP${App.gamepadLabelRT} ${Lang["GAME_INVENTORY"]}" "$SP${App.gamepadLabelRT} ${Lang["GAME_INVENTORY"]}"
private val transitionalSearch = UIWorldPortalSearch(this) val transitionalSearch = UIWorldPortalSearch(this)
private val transitionalListing = UIWorldPortalListing(this) val transitionalListing = UIWorldPortalListing(this)
private val transitionalCargo = UIWorldPortalCargo(this) // val transitionalCargo = UIWorldPortalCargo(this)
private val transitionPanel = UIItemHorizontalFadeSlide( private val transitionPanel = UIItemHorizontalFadeSlide(
this, this,
(width - internalWidth) / 2, (width - internalWidth) / 2,
INVENTORY_CELLS_OFFSET_Y(), INVENTORY_CELLS_OFFSET_Y(),
width, width,
App.scr.height, App.scr.height,
1f, 0f,
transitionalSearch, transitionalListing, transitionalCargo transitionalListing, transitionalSearch
) )
/**
* Called by:
* - "Search" button on UIWorldPortalListing
* - "Cancel" button on UIWorldPortalSearch
*/
fun requestTransition(target: Int) = transitionPanel.requestTransition(target)
init { init {
addUIitem(catBar)
addUIitem(transitionPanel) addUIitem(transitionPanel)
} }
internal var xEnd = (width + internalWidth).div(2).toFloat() internal var xEnd = (width + internalWidth).div(2).toFloat()
@@ -99,57 +88,133 @@ class UIWorldPortal : UICanvas(
override fun updateUI(delta: Float) { override fun updateUI(delta: Float) {
transitionPanel.update(delta)
} }
override fun renderUI(batch: SpriteBatch, camera: Camera) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
drawBackground(batch, handler.opacity) drawBackground(batch, 1f)
// UI items // UI items
catBar.render(batch, camera)
transitionPanel.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() { override fun show() {
super.show() super.show()
transitionPanel.forcePosition(0)
transitionPanel.show() transitionPanel.show()
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
// add current world to the player's worldportaldict
addWorldToPlayersDict(INGAME.world.worldIndex)
cleanUpWorldDict()
}
override fun hide() {
transitionPanel.hide()
} }
override fun dispose() { override fun dispose() {
catBar.dispose() transitionPanel.dispose()
}
fun resetUI() {
} }
override fun doOpening(delta: Float) { override fun doOpening(delta: Float) {
super.doOpening(delta) super.doOpening(delta)
resetUI() transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
INGAME.pause() INGAME.pause()
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
} }
override fun doClosing(delta: Float) { override fun doClosing(delta: Float) {
super.doClosing(delta) super.doClosing(delta)
transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
INGAME.resume() INGAME.resume()
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
} }
override fun endOpening(delta: Float) { override fun endOpening(delta: Float) {
super.endOpening(delta) super.endOpening(delta)
transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
UIItemInventoryItemGrid.tooltipShowing.clear() UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required! INGAME.setTooltipMessage(null) // required!
} }
override fun endClosing(delta: Float) { override fun endClosing(delta: Float) {
super.endClosing(delta) super.endClosing(delta)
resetUI() transitionPanel.uis.forEach { it.opacity = FastMath.pow(opacity, 0.5f) }
UIItemInventoryItemGrid.tooltipShowing.clear() UIItemInventoryItemGrid.tooltipShowing.clear()
INGAME.setTooltipMessage(null) // required! 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( class UIItemWorldPortalTopBar(
@@ -171,7 +236,7 @@ class UIItemWorldPortalTopBar(
private val genericIcons: TextureRegionPack = CommonResourcePool.getAsTextureRegionPack("inventory_category") private val genericIcons: TextureRegionPack = CommonResourcePool.getAsTextureRegionPack("inventory_category")
private val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons") private val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons")
private val catIconImages = listOf( /*private val catIconImages = listOf(
icons.get(0, 0), icons.get(0, 0),
genericIcons.get(16,0), genericIcons.get(16,0),
icons.get(1, 0), icons.get(1, 0),
@@ -182,15 +247,21 @@ class UIItemWorldPortalTopBar(
"CONTEXT_WORLD_SEARCH", "CONTEXT_WORLD_SEARCH",
"", "",
"CONTEXT_WORLD_LIST", "CONTEXT_WORLD_LIST",
"GAME_INVENTORY",
"", "",
) "GAME_INVENTORY",
)*/
private val buttonGapSize = 120 private val buttonGapSize = 120
private val highlighterYPos = icons.tileH + 4 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 val xoff = if (it == 1) -32 else if (it == 3) 32 else 0
UIItemImageButton( UIItemImageButton(
parentUI, 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) { override fun render(batch: SpriteBatch, camera: Camera) {
super.render(batch, camera) super.render(batch, camera)
// button // button
buttons.forEach { it.render(batch, camera) } /*buttons.forEach { it.render(batch, camera) }
// label // label
batch.color = Color.WHITE batch.color = Color.WHITE
val text = Lang[catIconLabels[selection]] val text = Lang[catIconLabels[selectedPanel]]
App.fontGame.draw(batch, text, buttons[selection].posX + 10 - (App.fontGame.getWidth(text) / 2), posY + highlighterYPos + 4) App.fontGame.draw(batch, text, buttons[selectedPanel].posX + 10 - (App.fontGame.getWidth(text) / 2), posY + highlighterYPos + 4)
*/
blendNormalStraightAlpha(batch) blendNormalStraightAlpha(batch)

View File

@@ -1,25 +1,252 @@
package net.torvald.terrarum.modulebasegame.ui package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch 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.Toolkit
import net.torvald.terrarum.ui.UICanvas 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 width: Int = Toolkit.drawWidth
override var height: Int = App.scr.height override var height: Int = App.scr.height
override fun updateUI(delta: Float) { lateinit var chestInventory: FixtureInventory
TODO("Not yet implemented") 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) { 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() { 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.graphics.g2d.TextureRegion
import com.badlogic.gdx.utils.GdxRuntimeException import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.langpack.Lang 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.INVENTORY_CELLS_OFFSET_Y
import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.getCellCountVertically import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull.Companion.getCellCountVertically
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
@@ -28,8 +31,16 @@ import java.time.Instant
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import java.util.zip.GZIPInputStream 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. * Created by minjaesong on 2023-05-19.
*/ */
class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
@@ -47,31 +58,67 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
private val textAreaW = thumbw - 32 private val textAreaW = thumbw - 32
private val thumbh = 252 private val thumbh = 252
private val hx = Toolkit.drawWidth.div(2) 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 listCount = getCellCountVertically(UIItemWorldCellsSimple.height, gridGap)
private val listHeight = UIItemWorldCellsSimple.height + (listCount - 1) * (UIItemWorldCellsSimple.height + gridGap) private val listHeight = UIItemWorldCellsSimple.height + (listCount - 1) * (UIItemWorldCellsSimple.height + gridGap)
private val memoryGaugeWidth = textAreaW private val memoryGaugeWidth = textAreaW
private val deleteButtonWidth = (thumbw - gridGap) / 2 private val deleteButtonWidth = (thumbw - gridGap) / 2
private val buttonDeleteWorld = UIItemTextButton(this, private val buttonsY = y + listHeight + gridGap
"MENU_LABEL_DELETE",
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, hx - gridGap/2 - deleteButtonWidth,
y + listHeight - buttonHeight, buttonsY,
deleteButtonWidth, deleteButtonWidth,
readFromLang = true, readFromLang = true,
hasBorder = true, hasBorder = true,
alignment = UIItemTextButton.Companion.Alignment.CENTRE alignment = UIItemTextButton.Companion.Alignment.CENTRE
) ).also {
private val buttonRenameWorld = UIItemTextButton(this, 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", "MENU_LABEL_RENAME",
buttonDeleteWorld.posX - gridGap - deleteButtonWidth, hx + gridGap/2,
y + listHeight - buttonHeight, buttonsY,
deleteButtonWidth, deleteButtonWidth,
readFromLang = true, readFromLang = true,
hasBorder = true, hasBorder = true,
alignment = UIItemTextButton.Companion.Alignment.CENTRE 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>() private val worldList = ArrayList<WorldInfo>()
data class 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 { init {
CommonResourcePool.addToLoadingList("terrarum-basegame-worldportalicons") { CommonResourcePool.addToLoadingList("terrarum-basegame-worldportalicons") {
TextureRegionPack(ModMgr.getGdxFile("basegame", "gui/worldportal_catbar.tga"), 30, 20) TextureRegionPack(ModMgr.getGdxFile("basegame", "gui/worldportal_catbar.tga"), 30, 20)
} }
CommonResourcePool.loadAll() CommonResourcePool.loadAll()
navRemoCon.scrollUpListener = { _,_ -> scrollItemPage(-1) }
navRemoCon.scrollDownListener = { _,_ -> scrollItemPage(1) }
addUIitem(buttonRenameWorld) addUIitem(buttonDelete)
addUIitem(buttonDeleteWorld) addUIitem(buttonRename)
addUIitem(buttonTeleport)
addUIitem(buttonSearch)
addUIitem(navRemoCon)
} }
private var chunksUsed = 0 fun scrollItemPage(relativeAmount: Int) {
private val chunksMax = 100000 listPage = if (listPageCount == 0) 0 else (listPage + relativeAmount).fmod(listPageCount)
}
private lateinit var worldCells: Array<UIItemWorldCellsSimple> private fun readWorldList() {
private var selected: UIItemWorldCellsSimple? = null
private var selectedIndex: Int? = null
override fun show() {
worldList.clear() worldList.clear()
(INGAME.actorGamer.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(",").filter { it.isNotBlank() }.map { (INGAME.actorGamer.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(",").filter { it.isNotBlank() }.map {
it.ascii85toUUID().let { it to App.savegameWorlds[it] } 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 chunksCount = 0
var seed = 0L var seed = 0L
var lastPlayed = 0L var lastPlayed = 0L
@@ -122,6 +191,8 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
var h = 0 var h = 0
var thumb: TextureRegion? = null var thumb: TextureRegion? = null
disk.rebuild()
JsonFetcher.readFromJsonString(ByteArray64Reader(disk!!.requestFile(-1)!!.contents.getContent() as ByteArray64, Common.CHARSET)).let { JsonFetcher.readFromJsonString(ByteArray64Reader(disk!!.requestFile(-1)!!.contents.getContent() as ByteArray64, Common.CHARSET)).let {
JsonFetcher.forEachSiblings(it) { name, value -> JsonFetcher.forEachSiblings(it) { name, value ->
if (name == "width") w = value.asInt() if (name == "width") w = value.asInt()
@@ -148,31 +219,63 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
}.let { }.let {
worldList.addAll(it) worldList.addAll(it)
} }
chunksUsed = worldList.sumOf { it.dimensionInChunks } 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( UIItemWorldCellsSimple(
this, this,
hx + gridGap / 2, hx + gridGap / 2,
y + (gridGap + UIItemWorldCellsSimple.height) * it, y + (gridGap + UIItemWorldCellsSimple.height) * it0,
worldList.getOrNull(it), worldList.getOrNull(it),
worldList.getOrNull(it)?.diskSkimmer?.getDiskName(Common.CHARSET) worldList.getOrNull(it)?.diskSkimmer?.getDiskName(Common.CHARSET)
).also { button -> ).also { button ->
button.clickOnceListener = { _, _, _ -> button.clickOnceListener = { _, _ ->
selected = button selected = button
selectedIndex = it selectedIndex = it
highlightListEditButtons(worldList.getOrNull(it))
updateUIbyButtonSelection() updateUIbyButtonSelection()
} }
} }
} }
}
override fun show() {
listPage = 0
readWorldList()
rebuildList()
uiItems.forEach { it.show() } uiItems.forEach { it.show() }
worldCells.forEach { it.show() } worldCells.forEach { it.show() }
selected = null selected = null
disableListEditButtons()
updateUIbyButtonSelection() updateUIbyButtonSelection()
} }
@@ -200,7 +303,11 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
override fun updateUI(delta: Float) { override fun updateUI(delta: Float) {
uiItems.forEach { it.update(delta) } 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 private val iconGap = 12f
@@ -231,7 +338,7 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons") val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons")
override fun renderUI(batch: SpriteBatch, camera: Camera) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
val memoryGaugeXpos = hx - memoryGaugeWidth - gridGap/2 val memoryGaugeXpos = hx - memoryGaugeWidth - gridGap/2
val memoryGaugeYpos = y + listHeight - buttonHeight - gridGap - buttonHeight val memoryGaugeYpos = y + listHeight - buttonHeight
val textXpos = memoryGaugeXpos + 3 val textXpos = memoryGaugeXpos + 3
// draw background // // draw background //
@@ -291,7 +398,7 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
uiItems.forEach { it.render(batch, camera) } uiItems.forEach { it.render(batch, camera) }
worldCells.forEach { it.render(batch, camera) } if (::worldCells.isInitialized) worldCells.forEach { it.render(batch, camera) }
// control hints // control hints
batch.color = Color.WHITE batch.color = Color.WHITE
@@ -300,15 +407,15 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() {
override fun hide() { override fun hide() {
uiItems.forEach { it.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) {} } worldList.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} }
} }
override fun dispose() { override fun dispose() {
uiItems.forEach { it.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) {} } 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 { override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
if (this.isVisible) { if (this.isVisible) {
uiItems.forEach { it.touchUp(screenX, screenY, pointer, button) } 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) } handler.subUIs.forEach { it.touchUp(screenX, screenY, pointer, button) }
return true return true
} }
else return false 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( class UIItemWorldCellsSimple(
parent: UIWorldPortalListing, val parent: UIWorldPortalListing,
initialX: Int, initialX: Int,
initialY: Int, initialY: Int,
internal val worldInfo: UIWorldPortalListing.WorldInfo? = null, 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() { override fun dispose() {
} }
} }

View File

@@ -1,28 +1,174 @@
package net.torvald.terrarum.modulebasegame.ui package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera 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.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.App
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.ui.UICanvas 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. * Created by minjaesong on 2023-05-19.
*/ */
class UIWorldPortalSearch(val full: UIWorldPortal) : UICanvas() { class UIWorldPortalSearch(val full: UIWorldPortal) : UICanvas() {
override var width: Int = Toolkit.drawWidth // override var width: Int = Toolkit.drawWidth
override var height: Int = App.scr.height // 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) { override fun updateUI(delta: Float) {
TODO("Not yet implemented") uiItems.forEach { it.update(delta) }
} }
override fun renderUI(batch: SpriteBatch, camera: Camera) { 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() { 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() { fun checkFileSanity() {
if (!diskFile.exists()) throw NoSuchFileException(diskFile.absoluteFile) 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 // check magic
val fis = FileInputStream(diskFile) val fis = FileInputStream(diskFile)
val magic = ByteArray(4).let { fis.read(it); it } 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() fis.close()
} }
@@ -221,7 +221,7 @@ removefile:
fa.read(4).toIntBig().toLong() fa.read(4).toIntBig().toLong()
} }
DiskEntry.SYMLINK -> 8L 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) 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) 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 * Throws an exception if specified size cannot fit into the disk
*/ */
fun VirtualDisk.checkCapacity(newSize: Long) { fun VirtualDisk.checkCapacity(newSize: Long) {
if (this.usedBytes + newSize > this.capacity) // if (this.usedBytes + newSize > this.capacity)
throw IOException("Not enough space on the disk") // throw IOException("Not enough space on the disk")
} }
fun ByteArray64.toIntBig(): Int { fun ByteArray64.toIntBig(): Int {
if (this.size != 4L) 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 Int8 Disk properties flag 1
0th bit: readonly 0th bit: readonly
Int8 Save type (0b 0000 00ab) 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 a: set - generated by autosave
Int8 Kind of the Save file Int8 Kind of the Save file
0: Undefined (or very old version of the game) 0: Undefined (or very old version of the game)
@@ -145,9 +145,7 @@ class VirtualDisk(
var extraInfoBytes = ByteArray(16) var extraInfoBytes = ByteArray(16)
val entries = HashMap<EntryID, DiskEntry>() val entries = HashMap<EntryID, DiskEntry>()
var isReadOnly: Boolean val isReadOnly = false
set(value) { extraInfoBytes[0] = (extraInfoBytes[0] and 0xFE.toByte()) or value.toBit() }
get() = capacity == 0L || (extraInfoBytes.size > 0 && extraInfoBytes[0].and(1) == 1.toByte())
var saveMode: Int var saveMode: Int
set(value) { extraInfoBytes[1] = value.toByte() } set(value) { extraInfoBytes[1] = value.toByte() }
get() = extraInfoBytes[1].toUint() get() = extraInfoBytes[1].toUint()
@@ -260,6 +258,7 @@ object VDFileID {
const val SPRITEDEF = -2L const val SPRITEDEF = -2L
const val SPRITEDEF_GLOW = -3L const val SPRITEDEF_GLOW = -3L
const val LOADORDER = -4L const val LOADORDER = -4L
const val PLAYER_SCREENSHOT = -5L
const val BODYPART_TO_ENTRY_MAP = -1025L const val BODYPART_TO_ENTRY_MAP = -1025L
const val BODYPARTGLOW_TO_ENTRY_MAP = -1026L const val BODYPARTGLOW_TO_ENTRY_MAP = -1026L
} }
@@ -279,6 +278,11 @@ fun diskIDtoReadableFilename(id: EntryID, saveKind: Int?): String = when (id) {
"spritedef-glow" "spritedef-glow"
else else
"file #$id" "file #$id"
VDFileID.PLAYER_SCREENSHOT ->
if (saveKind == PLAYER_DATA)
"screenshot.tga.gz"
else
"file #$id"
VDFileID.LOADORDER -> "loadOrder.txt" VDFileID.LOADORDER -> "loadOrder.txt"
// -16L -> "blockcodex.json.gz" // -16L -> "blockcodex.json.gz"
// -17L -> "itemcodex.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) menuBar.add(menuEdit)
val menuManage = JMenu("Manage") val menuManage = JMenu("Manage")
@@ -638,8 +603,7 @@ class VirtualDiskCracker(val sysCharset: Charset = Charsets.UTF_8) : JFrame() {
} }
private fun getDiskInfoText(disk: VirtualDisk): String { private fun getDiskInfoText(disk: VirtualDisk): String {
return """Name: ${String(disk.diskName, sysCharset)} return """Name: ${String(disk.diskName, sysCharset)}
Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free) Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free)"""
Write protected: ${disk.isReadOnly.toEnglish()}"""
} }

View File

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

View File

@@ -377,7 +377,7 @@ class BasicDebugInfoWindow : UICanvas() {
blendNormalStraightAlpha(batch) blendNormalStraightAlpha(batch)
batch.end() /*batch.end()
gdxBlendNormalStraightAlpha() gdxBlendNormalStraightAlpha()
Terrarum.inShapeRenderer { Terrarum.inShapeRenderer {
it.color = uiColour 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.line(uiX + halfW, App.scr.height - (uiY + halfH), uiX + halfW + pointDX, App.scr.height - (uiY + halfH + pointDY))
it.color = Color.GRAY 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) 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 { init {
reset() reset()
addUIitem(textinput) addUIitem(textinput)
textinput.isActive = false textinput.isEnabled = false
} }
private val lb = ArrayList<String>() private val lb = ArrayList<String>()
@@ -99,7 +99,7 @@ class ConsoleWindow : UICanvas() {
clickLatched = false clickLatched = false
} }
textinput.isActive = (isOpened && !isClosing) textinput.isEnabled = (isOpened && !isClosing)
} }
override fun renderUI(batch: SpriteBatch, camera: Camera) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
@@ -269,14 +269,14 @@ class ConsoleWindow : UICanvas() {
drawOffY = MovementInterpolator.fastPullOut(openingTimeCounter.toFloat() / openCloseTime.toFloat(), drawOffY = MovementInterpolator.fastPullOut(openingTimeCounter.toFloat() / openCloseTime.toFloat(),
0f, -height.toFloat() 0f, -height.toFloat()
)*/ )*/
textinput.isActive = false textinput.isEnabled = false
textinput.mouseoverUpdateLatch = false textinput.mouseoverUpdateLatch = false
} }
override fun endOpening(delta: Float) { override fun endOpening(delta: Float) {
drawOffY = 0f drawOffY = 0f
openingTimeCounter = 0f openingTimeCounter = 0f
textinput.isActive = true textinput.isEnabled = true
textinput.mouseoverUpdateLatch = true textinput.mouseoverUpdateLatch = true
} }

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.ui
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.* 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.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.graphics.glutils.FloatFrameBuffer import com.badlogic.gdx.graphics.glutils.FloatFrameBuffer
@@ -9,6 +10,7 @@ import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -28,6 +30,8 @@ object Toolkit : Disposable {
val COL_SELECTED = Color(0x00f8ff_ff) // cyan, HIGHLY SATURATED 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_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_DISABLED = Color(0xaaaaaaff.toInt())
val COL_RED = Color(0xff8888ff.toInt())
val COL_REDD = Color(0xff4448ff.toInt())
/* /*
Try this for alt colour set: 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 get() = App.scr.width - if (App.getConfigBoolean("fx_streamerslayout")) App.scr.chatWidth else 0
val drawWidthf: Float val drawWidthf: Float
get() = drawWidth.toFloat() 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) { fun drawCentered(batch: SpriteBatch, image: Texture, screenPosY: Int, ui: UICanvas? = null) {
val imageW = image.width val imageW = image.width
@@ -114,6 +122,11 @@ object Toolkit : Disposable {
batch.draw(image, targetW.minus(imageW).ushr(1).toFloat() + offsetX, screenPosY.toFloat() + offsetY) 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) { fun fillArea(batch: SpriteBatch, x: Int, y: Int, w: Int, h: Int) {
batch.draw(textureWhiteSquare, x.toFloat(), y.toFloat(), w.toFloat(), h.toFloat()) 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()) (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 // centre area
/*batch.draw(baloonTile.get(1, 1), x, y, w, h) /*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.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) 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) 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 errored = false
private var normalCol = Color.WHITE private var normalCol = Color.WHITE
private var errorCol = Color(0xFF8888FF.toInt()) private var errorCol = Toolkit.Theme.COL_RED
override fun updateUI(delta: Float) { override fun updateUI(delta: Float) {
spinnerTimer += delta 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 */ /** 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 */ /** 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** /** **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) 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 * 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.Camera
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import net.torvald.terrarum.App
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent 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 protected var mouseLatched = Terrarum.mouseDown
/** UI to call (show up) while mouse is up */ /** UI to call (show up) while mouse is up */
open val mouseOverCall: UICanvas? = null open var mouseOverCall: UICanvas? = null
// kind of listener implementation // kind of listener implementation
/** Fired once for every update /** Fired once for every update
* Parameter: delta */ * Parameter: delta */
open var updateListener: ((Float) -> Unit)? = null open var updateListener: ((Float) -> Unit) = {}
/** Parameter: keycode */ /** Parameter: keycode */
open var keyDownListener: ((Int) -> Unit)? = null open var keyDownListener: ((Int) -> Unit) = {}
/** Parameter: keycode */ /** Parameter: keycode */
open var keyUpListener: ((Int) -> Unit)? = null open var keyUpListener: ((Int) -> Unit) = {}
open var keyTypedListener: ((Char) -> Unit)? = null open var keyTypedListener: ((Char) -> Unit) = {}
open var touchDraggedListener: ((Int, Int, Int) -> Unit)? = null open var touchDraggedListener: ((Int, Int, Int) -> Unit) = { _,_,_ -> }
/** Parameters: screenX, screenY, pointer, button */ /** Parameters: screenX, screenY, pointer, button */
open var touchDownListener: ((Int, Int, Int, Int) -> Unit)? = null @Deprecated("Not really deprecated but you MOST DEFINITELY want to use clickOnceListener(mouseX, mouseY) instead.")
open var touchUpListener: ((Int, Int, Int, Int) -> Unit)? = null open var touchDownListener: ((Int, Int, Int, Int) -> Unit) = { _,_,_,_ -> }
open var touchUpListener: ((Int, Int, Int, Int) -> Unit) = { _,_,_,_ -> }
/** Parameters: amountX, amountY */ /** Parameters: amountX, amountY */
open var scrolledListener: ((Float, Float) -> Unit)? = null open var scrolledListener: ((Float, Float) -> Unit) = { _,_ -> }
/** Parameters: relative mouseX, relative mouseY, button /** 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! * 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 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 /** 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. */ * 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 * Whether the button is "available" or not to the player
*/ */
open var isEnabled = true
/**
* Whether the button should receive updates
*/
open var isActive = true 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) { open fun update(delta: Float) {
if (parentUI.isVisible) { if (parentUI.isVisible) {
if (updateListener != null) {
updateListener!!.invoke(delta)
}
if (isActive) { if (isActive) {
updateListener.invoke(delta)
mouseOverCall?.update(delta) mouseOverCall?.update(delta)
if (mouseUp) { if (mouseUp) {
if (mouseOverCall?.isVisible ?: false) { if (mouseOverCall?.isVisible == true) {
mouseOverCall?.setAsOpen() mouseOverCall?.setAsOpen()
} }
mouseOverCall?.updateUI(delta) mouseOverCall?.updateUI(delta)
mouseUpListener.invoke(itemRelativeMouseX, itemRelativeMouseY)
} }
else { else {
if (mouseOverCall?.isVisible ?: false) { if (mouseOverCall?.isVisible == true) {
mouseOverCall?.setAsClose() 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) { open fun render(batch: SpriteBatch, camera: Camera) {
if (parentUI.isVisible) { if (parentUI.isVisible) {
if (isActive) { // if (isActive) {
mouseOverCall?.render(batch, camera) mouseOverCall?.render(batch, camera)
if (mouseUp) { if (mouseUp) {
mouseOverCall?.renderUI(batch, camera) mouseOverCall?.renderUI(batch, camera)
} }
} // }
} }
} }
// keyboard controlled // keyboard controlled
open fun keyDown(keycode: Int): Boolean { open fun keyDown(keycode: Int): Boolean {
if (parentUI.isVisible && keyDownListener != null && isActive) { if (parentUI.isVisible && isEnabled) {
keyDownListener!!.invoke(keycode) keyDownListener.invoke(keycode)
return true return true
} }
return false return false
} }
open fun keyUp(keycode: Int): Boolean { open fun keyUp(keycode: Int): Boolean {
if (parentUI.isVisible && keyUpListener != null && isActive) { if (parentUI.isVisible && isEnabled) {
keyUpListener!!.invoke(keycode) keyUpListener.invoke(keycode)
return true return true
} }
return false return false
} }
open fun keyTyped(character: Char): Boolean { open fun keyTyped(character: Char): Boolean {
if (parentUI.isVisible && keyTypedListener != null && isActive) { if (parentUI.isVisible && isEnabled) {
keyTypedListener!!.invoke(character) keyTypedListener.invoke(character)
return true return true
} }
@@ -205,8 +212,8 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
// mouse controlled // mouse controlled
open fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean { open fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean {
if (parentUI.isVisible && touchDraggedListener != null && isActive) { if (parentUI.isVisible && isEnabled) {
touchDraggedListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer) touchDraggedListener.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer)
return true 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 { open fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
var actionDone = false var actionDone = false
if (parentUI.isVisible && isActive) { if (parentUI.isVisible && isEnabled) {
if (touchDownListener != null && mouseUp) { if (mouseUp) {
touchDownListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button) touchDownListener.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button)
actionDone = true actionDone = true
} }
if (clickOnceListener != null && !clickOnceListenerFired && mouseUp) { if (!clickOnceListenerFired && mouseUp && button == App.getConfigInt("config_mouseprimary")) {
clickOnceListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, button) clickOnceListener.invoke(itemRelativeMouseX, itemRelativeMouseY)
actionDone = true 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 { open fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
clickOnceListenerFired = false clickOnceListenerFired = false
if (parentUI.isVisible && touchUpListener != null && mouseUp) { if (parentUI.isVisible && mouseUp) {
touchUpListener!!.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button) touchUpListener.invoke(itemRelativeMouseX, itemRelativeMouseY, pointer, button)
return true return true
} }
return false return false
} }
open fun scrolled(amountX: Float, amountY: Float): Boolean { open fun scrolled(amountX: Float, amountY: Float): Boolean {
if (parentUI.isVisible && scrolledListener != null && mouseUp && isActive) { if (parentUI.isVisible && mouseUp && isEnabled) {
scrolledListener!!.invoke(amountX, amountY) scrolledListener.invoke(amountX, amountY)
return true return true
} }

View File

@@ -18,7 +18,7 @@ class UIItemHorizontalFadeSlide(
//transitionLength: Float, //transitionLength: Float,
currentPosition: Float, currentPosition: Float,
vararg uis: UICanvas 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 getOffX(index: Int) = ((currentPosition - index) * width / 2f).roundToInt()
fun getOpacity(index: Int) = 1f - (currentPosition - index).absoluteValue.coerceIn(0f, 1f) 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.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.BlendMode import net.torvald.terrarum.BlendMode
import net.torvald.terrarum.abs
import net.torvald.terrarum.blendNormalStraightAlpha import net.torvald.terrarum.blendNormalStraightAlpha
/** /**
@@ -33,14 +34,24 @@ open class UIItemImageButton(
initialX: Int, initialX: Int,
initialY: Int, initialY: Int,
/** this does NOT resize the image; use imageDrawWidth to actually resize the image */
override val width: Int = image.regionWidth, 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, override val height: Int = image.regionHeight,
/** When clicked, toggle its "lit" status */ /** 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) { ) : UIItem(parent, initialX, initialY) {
var highlighted = false var highlighted = false
var extraDrawOp: (UIItem, SpriteBatch) -> Unit = { _,_ -> }
override fun render(batch: SpriteBatch, camera: Camera) { override fun render(batch: SpriteBatch, camera: Camera) {
// draw background // draw background
@@ -60,15 +71,22 @@ open class UIItemImageButton(
Toolkit.fillArea(batch, posX.toFloat(), posY.toFloat(), width.toFloat(), height.toFloat()) Toolkit.fillArea(batch, posX.toFloat(), posY.toFloat(), width.toFloat(), height.toFloat())
} }
blendNormalStraightAlpha(batch)
// draw image // 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 batch.color = if (highlighted) highlightCol
else if (mouseUp) activeCol else if (mouseUp) activeCol
else inactiveCol else inactiveCol
extraDrawOp(this, batch)
batch.draw(image, (posX + (width - image.regionWidth) / 2).toFloat(), (posY + (height - image.regionHeight) / 2).toFloat())
} }
override fun dispose() { 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() { override fun dispose() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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