slow.monster

STUDIO HAZE · jeniapp

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20.2 · 63 files

apps/slow-monster/core/state.tpz

Topaz · 26171 B · Original file

1// 게임 상태 레코드와 STATE 문자열의 직렬화, 복원을 둡니다. 게임 전체가 월드시드와 입력 순서의 순수 함수입니다.
2// STATE 는 파이프로 나눈 한 줄입니다. v8 순서는 버전, 시드, 스텝, x, y, 방향, 맵, 장면, 야생 여섯 칸, 스포어볼, 포획카운터, 파티(카운트 + 11칸씩), 보관함, 파티커서, 도감커서, 처치 목록(카운트 접두), 초점, "본것:대기", 계통 노드(카운트 + 7칸씩), 진화 11칸, 배틀 9칸이고 배틀 메시지가 맨 끝입니다.
3// 시계, 파일, 시드 없는 난수를 쓰지 않아 같은 입력은 늘 같은 상태입니다. 복원한 상태는 늘 v8 로 다시 직렬화됩니다.
4// v7 부터 v0 까지 옛 저장을 읽고, 손상되거나 조작된 값은 가두거나 기본값으로 되돌려 아래쪽 정수 계산이 넘치지 않게 합니다. 빈 문자열과 일곱 칸보다 짧은 저장은 오류로 돌려 진입점이 기본 상태로 닫습니다.
5// Game state record and the pipe-separated STATE string: serialize, and restore every save version from v0 to v8 with clamping.
7import core.encounter { 야생크리처 }
8import core.creature
9import core.battle
10import core.evolution
11import core.genome
12import core.util { 범위가두기, 복사추가 }
13import data.species
14import data.items { 시작스포어볼 }
16// 차면 잡은 야생은 보관함으로 갑니다.
17export let 파티최대 = 6
19// 정상 플레이는 닿지 않는 상한이라 범위 안의 값은 그대로 지나갑니다.
20// 좌표 9999 는 24x24 맵을 한참 넘고, 그리기의 (x - 카메라) * 16 과 이동의 ±1 이 넘치지 않습니다.
21let 좌표상한 = 9999
22// 스텝, 경험, 포획과 전투 카운터, 체력의 상한입니다. 하류의 +1 과 + 적레벨 * 14 가 넘치지 않습니다.
23let 카운터상한 = 1000000000
25function 좌표가두기(값: int) -> int {
26 범위가두기(값, 0, 좌표상한)
29function 카운터가두기(값: int) -> int {
30 범위가두기(값, 0, 카운터상한)
33// 비트 연산이 없어 산술 비트셋이고 열여섯 비트로 가둡니다. 토파즈 과제 '비트 연산' 의 우회입니다.
34export function 비트가두기(값: int) -> int {
35 범위가두기(값, 0, 65535)
38export type 방향 = "아래" | "위" | "왼쪽" | "오른쪽"
40// 저장 문자열과 그리기 목록의 방향 번호는 0 아래, 1 위, 2 왼쪽, 3 오른쪽입니다. 그 밖의 번호는 아래로 읽습니다.
41function 방향읽기(번호: int) -> 방향 {
42 match 번호 {
43 case 1 => "위"
44 case 2 => "왼쪽"
45 case 3 => "오른쪽"
46 case _ => "아래"
47 }
50export function 방향번호(방향값: 방향) -> int {
51 match 방향값 {
52 case "아래" => 0
53 case "위" => 1
54 case "왼쪽" => 2
55 case "오른쪽" => 3
56 }
59export type 플레이어 = { x: int, y: int, 방향: 방향 }
61// 플레이어가 마주 본 칸입니다.
62export function 마주x(대상: 플레이어) -> int {
63 match 대상.방향 {
64 case "왼쪽" => 대상.x - 1
65 case "오른쪽" => 대상.x + 1
66 case "아래" | "위" => 대상.x
67 }
70export function 마주y(대상: 플레이어) -> int {
71 match 대상.방향 {
72 case "위" => 대상.y - 1
73 case "아래" => 대상.y + 1
74 case "왼쪽" | "오른쪽" => 대상.y
75 }
78export type 장면 = "field" | "battle" | "party" | "dex" | "story" | "evolve"
80function 장면파싱(토큰: string) -> 장면 {
81 match 토큰 {
82 case "battle" => "battle"
83 case "party" => "party"
84 case "dex" => "dex"
85 case "story" => "story"
86 case "evolve" => "evolve"
87 case _ => "field"
88 }
91// 후보는 저장하지 않고 부모 스냅샷에서 다시 유도합니다. 모핑은 고정 직후 한 프레임이며 페이지가 장면을 재생합니다.
92export type 진화단계 = "none" | "choose" | "morph"
94function 진화단계파싱(토큰: string) -> 진화단계 {
95 match 토큰 {
96 case "choose" => "choose"
97 case "morph" => "morph"
98 case _ => "none"
99 }
102export type 진화상태 = {
103 단계: 진화단계,
104 부모형태: string,
105 부모게놈: genome.게놈,
106 부모계통시드: string,
107 부모진화카운터: int,
108 부모세대: int,
109 부모고유번호: int,
110 선택커서: int,
111 잠긴분기: int,
112 결과형태: string,
113 결과게놈: genome.게놈,
116// 본것은 한 번씩만 띄우는 이정표, 대기는 보여 줄 카드 큐이고 둘 다 비트셋입니다.
117export type 스토리상태 = { 본것: int, 대기: int }
119// 배틀, 드리프트, 진화는 파티[초점] 한 마리만 합니다. 잡은 동료마다 제 계통의 뿌리라 도감이 숲이 됩니다.
120export type 상태 = {
121 버전: int,
122 월드시드: string,
123 스텝: int,
124 플레이어: 플레이어,
125 맵아이디: string,
126 장면: 장면,
127 야생: 야생크리처,
128 스포어볼: int,
129 포획카운터: int,
130 파티: Array<creature.전투원>,
131 보관함: Array<creature.전투원>,
132 파티커서: int,
133 도감커서: int,
134 처치된것들: Array<int>,
135 초점: int,
136 스토리: 스토리상태,
137 계통노드들: Array<evolution.계통노드>,
138 진화: 진화상태,
139 배틀: battle.배틀상태,
142// 초점은 복원할 때 가두지만 한 번 더 가둡니다.
143export function 초점전투원(현재: 상태) -> creature.전투원 {
144 현재.파티[범위가두기(현재.초점, 0, 현재.파티.length - 1)]
147export function 빈진화() -> 진화상태 {
148 let 영게놈 = genome.게놈만들기(0, 0, 0, 0, 0)
149 {
150 단계: "none",
151 부모형태: "",
152 부모게놈: 영게놈,
153 부모계통시드: "",
154 부모진화카운터: 0,
155 부모세대: 0,
156 부모고유번호: 0,
157 선택커서: 0,
158 잠긴분기: 0,
159 결과형태: "",
160 결과게놈: 영게놈,
161 }
164export function 빈야생() -> 야생크리처 {
165 야생크리처 { 종아이디: 0, 이름: "", 레벨: 0, 체력: 0, 스프라이트: 0, 속성: 0 }
168export type 추가결과 = { 파티: Array<creature.전투원>, 보관함: Array<creature.전투원> }
170// 파티가 차 있으면 보관함에 붙이고 둘이 함께 늘지는 않습니다.
171export function 파티추가(파티: Array<creature.전투원>, 보관함: Array<creature.전투원>, 잡은것: creature.전투원) -> 추가결과 {
172 if 파티.length >= 파티최대 {
173 { 파티: 파티, 보관함: 복사추가(보관함, 잡은것) }
174 } else {
175 { 파티: 복사추가(파티, 잡은것), 보관함: 보관함 }
176 }
179// 계통수의 뿌리 노드입니다. 미진화면 종 이름을 형태이름으로 씁니다.
180export function 시작노드(전투원: creature.전투원, 스텝: int) -> evolution.계통노드 {
181 let 이름 = if 전투원.형태이름 != "" { 전투원.형태이름 } else { species.종찾기(전투원.종아이디).이름 }
182 evolution.노드생성(evolution.계통고유번호(전투원.계통시드), 0, 이름, genome.게놈요약(전투원.게놈), 전투원.세대, 스텝, 전투원.계통시드)
185// 같은 고유번호는 다시 붙이지 않아 여러 번 적용해도 같습니다.
186export function 노드추가(원본: Array<evolution.계통노드>, 노드: evolution.계통노드) -> Array<evolution.계통노드> {
187 if 노드.고유번호 in 원본.map(항목 => 항목.고유번호) { 원본.slice(0, 원본.length) } else { 복사추가(원본, 노드) }
190// 시작종은 "시드#starter" 계통시드를 받아 게놈이 월드시드의 함수가 되고, 계통 노드는 그 뿌리 하나로 시작합니다.
191export function 기본상태(시드: string) -> 상태 {
192 let 시작전투원 = creature.종에서전투원(species.시작종아이디(), 5, genome.시작계통시드(시드))
193 {
194 버전: 8,
195 월드시드: 시드,
196 스텝: 0,
197 플레이어: { x: 3, y: 5, 방향: "아래" },
198 맵아이디: "pond",
199 장면: "field",
200 야생: 빈야생(),
201 스포어볼: 시작스포어볼(),
202 포획카운터: 0,
203 처치된것들: [],
204 초점: 0,
205 스토리: { 본것: 0, 대기: 0 },
206 파티: [시작전투원],
207 보관함: [],
208 파티커서: 0,
209 도감커서: 0,
210 계통노드들: [시작노드(시작전투원, 0)],
211 진화: 빈진화(),
212 배틀: battle.빈배틀(),
213 }
216export function 처치추가(목록: Array<int>, 아이디: int) -> Array<int> {
217 if 아이디 in 목록 { 목록.slice(0, 목록.length) } else { 복사추가(목록, 아이디) }
220// 인물 앞에서 쉬면 파티 전체를 채워 긴 세션이 체력 소모로 무너지지 않게 합니다.
221export function 파티회복(현재: 상태) -> 상태 {
222 현재{ 파티: 현재.파티.map(creature.회복) }
225// 콜론으로 이어 파이프 없는 한 칸에 담습니다.
226function 게놈직렬(대상: genome.게놈) -> string {
227 "{대상.색소}:{대상.막}:{대상.크기}:{대상.대사}:{대상.촉수}"
230function 게놈역직렬(토큰: string) -> genome.게놈 {
231 let 칸들 = 토큰.split(":")
232 let 값들 = map(0..<5, i => if 칸들.length > i { toInt(칸들[i]) ?? 0 } else { 0 })
233 genome.게놈만들기(값들[0], 값들[1], 값들[2], 값들[3], 값들[4])
236// 계통시드와 형태이름은 파이프가 없는 문자열이고 형태이름 "" 은 미진화입니다.
237function 전투원직렬(전투원: creature.전투원) -> string {
238 "{전투원.종아이디}|{전투원.레벨}|{전투원.경험}|{전투원.현재체력}|{전투원.계통시드}|{전투원.드리프트}|{게놈직렬(전투원.게놈)}|{전투원.세대}|{전투원.진화카운터}|{전투원.형태이름}|{전투원.스탯부스트}"
241function 노드직렬(노드: evolution.계통노드) -> string {
242 "{노드.고유번호}|{노드.부모고유번호}|{노드.형태이름}|{노드.게놈요약}|{노드.세대}|{노드.발견스텝}|{노드.시드지문}"
245// 개수를 앞에 두어 가변 길이 구획이 스스로 끝을 알립니다.
246function 카운트접두(조각들: Array<string>) -> string {
247 if 조각들.length == 0 { "0" } else { "{조각들.length}|{조각들.join("|")}" }
250function 진화직렬(진: 진화상태) -> string {
251 "{진.단계}|{진.부모형태}|{게놈직렬(진.부모게놈)}|{진.부모계통시드}|{진.부모진화카운터}|{진.부모세대}|{진.부모고유번호}|{진.선택커서}|{진.잠긴분기}|{진.결과형태}|{게놈직렬(진.결과게놈)}"
254// 메시지에는 파이프가 없어 맨 끝에 두면 앞의 가변 구획 길이와 상관없이 읽힙니다.
255export function 직렬화(현재: 상태) -> string {
256 let 야생 = 현재.야생
257 let 배틀값 = 현재.배틀
258 let 앞부분 = "v{현재.버전}|{현재.월드시드}|{현재.스텝}|{현재.플레이어.x}|{현재.플레이어.y}|{방향번호(현재.플레이어.방향)}|{현재.맵아이디}|{현재.장면}"
259 let 야생부 = "{야생.종아이디}|{야생.레벨}|{야생.체력}|{야생.스프라이트}|{야생.속성}|{야생.이름}"
260 let 파티부 = "{현재.스포어볼}|{현재.포획카운터}|{카운트접두(현재.파티.map(전투원직렬))}|{카운트접두(현재.보관함.map(전투원직렬))}|{현재.파티커서}|{현재.도감커서}"
261 let 진행부 = "{카운트접두(현재.처치된것들.map(아이디 => "{아이디}"))}|{현재.초점}|{현재.스토리.본것}:{현재.스토리.대기}|{카운트접두(현재.계통노드들.map(노드직렬))}|{진화직렬(현재.진화)}"
262 let 배틀부 = "{배틀값.단계}|{배틀값.메뉴커서}|{배틀값.기술커서}|{배틀값.전투카운터}|{배틀값.적체력}|{배틀값.메시지커서}|{배틀값.다음단계}|{배틀값.결과}|{배틀값.메시지}"
263 "{앞부분}|{야생부}|{파티부}|{진행부}|{배틀부}"
266function 버전파싱(토큰: string) -> int {
267 toInt(토큰.replace("v", "")) ?? 3
270// 보폭 11 은 v5 이후, 6 은 v4, 4 는 v3 입니다. 보폭이 11 보다 작으면 미진화로 옮기고 v3 은 계통시드를 마이그레이션시드로 받습니다.
271// 세대 0 이면 전투원가두기가 게놈을 계통시드에서 다시 유도하므로 옛 저장의 게놈 인자는 쓰이지 않습니다.
272function 전투원파싱(칸들: Array<string>, 시작: int, 보폭: int, 마이그레이션시드: string) -> creature.전투원 {
273 let 종 = toInt(칸들[시작]) ?? species.시작종아이디()
274 let 레벨 = toInt(칸들[시작 + 1]) ?? 5
275 // 경험적용의 경험 + 얻은경험이 넘치지 않게 가둡니다. 나머지는 전투원가두기가 가둡니다.
276 let 경험 = 카운터가두기(toInt(칸들[시작 + 2]) ?? 0)
277 let 체력 = toInt(칸들[시작 + 3]) ?? 0
278 let 계통시드 = if 보폭 >= 6 { 칸들[시작 + 4] } else { 마이그레이션시드 }
279 let 드리프트 = if 보폭 >= 6 { toInt(칸들[시작 + 5]) ?? 0 } else { 0 }
280 let 세대 = if 보폭 >= 11 { toInt(칸들[시작 + 7]) ?? 0 } else { 0 }
281 let 진화카운터 = if 보폭 >= 11 { toInt(칸들[시작 + 8]) ?? 0 } else { 0 }
282 let 형태이름 = if 보폭 >= 11 { 칸들[시작 + 9] } else { "" }
283 let 스탯부스트 = if 보폭 >= 11 { toInt(칸들[시작 + 10]) ?? 0 } else { 0 }
284 let 기준게놈 = if 보폭 >= 11 { 게놈역직렬(칸들[시작 + 6]) } else { genome.게놈유도(계통시드) }
285 creature.전투원가두기(creature.빈전투원(){
286 종아이디: 종,
287 레벨: 레벨,
288 경험: 경험,
289 현재체력: 체력,
290 계통시드: 계통시드,
291 드리프트: 드리프트,
292 세대: 세대,
293 진화카운터: 진화카운터,
294 형태이름: 형태이름,
295 스탯부스트: 스탯부스트,
296 게놈: 기준게놈,
297 })
300type 배열파싱결과 = { 목록: Array<creature.전투원>, 다음: int }
302// 잘린 저장에서 파티가 칸을 다 쓰면 보관함의 카운트 자리가 끝 너머라 빈 목록으로 돌아갑니다.
303// 카운트는 실제로 남은 칸이 받쳐 주는 항목 수로 가두고, 파티는 비면 시작종 한 마리를 받습니다.
304function 배열파싱(칸들: Array<string>, 시작: int, 최소항목수: int, 보폭: int, 월드시드: string, 마이그레이션접두: string) -> 배열파싱결과 {
305 if 시작 < 0 || 시작 >= 칸들.length { return { 목록: [], 다음: 시작 } }
306 let 가용 = ((칸들.length - (시작 + 1)) / 보폭).atLeast(0)
307 let 카운트 = (toInt(칸들[시작]) ?? 0).atLeast(0).atMost(가용)
308 let 읽은목록 = map(0..<카운트, i => 전투원파싱(칸들, 시작 + 1 + i * 보폭, 보폭, "{월드시드}#legacy:{마이그레이션접두}:{i}"))
309 let 목록 = if 읽은목록.length < 최소항목수 { [creature.종에서전투원(species.시작종아이디(), 5, genome.시작계통시드(월드시드))] } else { 읽은목록 }
310 { 목록: 목록, 다음: 시작 + 1 + 카운트 * 보폭 }
313type 노드배열파싱결과 = { 목록: Array<evolution.계통노드>, 다음: int }
315// 도감커서가 끼어든 v5 인지 판정합니다. 계통 구획이 진화 블록 시작에서 정확히 끝나면 도감커서가 있는 배치입니다.
316function 계통섹션끝일치(칸들: Array<string>, 시작: int, 예상끝: int) -> bool {
317 if 시작 < 0 || 시작 >= 칸들.length { return false }
318 let 카운트 = toInt(칸들[시작]) ?? -1
319 if 카운트 < 0 { return false }
320 시작 + 1 + 카운트 * 7 == 예상끝
323// 배치는 노드마다 목록 전체를 훑어 제곱 시간이므로 여기서 128 개로 묶습니다. 실제 계통수는 끝까지 가도 수십 개입니다.
324function 노드배열파싱(칸들: Array<string>, 시작: int, 상한배제: int) -> 노드배열파싱결과 {
325 if 시작 < 0 || 시작 >= 칸들.length { return { 목록: [], 다음: 시작 } }
326 let 가용 = ((상한배제 - (시작 + 1)) / 7).atLeast(0)
327 let 카운트 = (toInt(칸들[시작]) ?? 0).atLeast(0).atMost(가용).atMost(128)
328 let 목록 = map(0..<카운트, i => 노드파싱(칸들, 시작 + 1 + i * 7))
329 { 목록: 목록, 다음: 시작 + 1 + 카운트 * 7 }
332function 노드파싱(칸들: Array<string>, 시작: int) -> evolution.계통노드 {
333 let 세대 = creature.세대가두기(toInt(칸들[시작 + 4]) ?? 0)
334 evolution.노드생성(toInt(칸들[시작]) ?? 0, toInt(칸들[시작 + 1]) ?? 0, 칸들[시작 + 2], 칸들[시작 + 3], 세대, toInt(칸들[시작 + 5]) ?? 0, 칸들[시작 + 6])
337type 처치파싱결과 = { 목록: Array<int>, 다음: int }
339// 카운트를 엔티티 수보다 한참 큰 64 로 가두고 범위 밖 아이디는 버립니다.
340function 처치파싱(칸들: Array<string>, 시작: int, 상한: int) -> 처치파싱결과 {
341 if 시작 < 0 || 시작 >= 칸들.length { return { 목록: [], 다음: 시작 } }
342 let 카운트 = (toInt(칸들[시작]) ?? 0).atLeast(0).atMost(64)
343 // 끝에서 센 위치에 진화 구획이 없으면 잘린 저장이므로 실제 남은 칸까지 살립니다.
344 let 진화있음 = 상한 > 시작 && 상한 < 칸들.length - 10 && (칸들[상한] == "none" || 칸들[상한] == "choose" || 칸들[상한] == "morph")
345 let 경계 = if 진화있음 { 상한 } else { 칸들.length }
346 let 끝 = (시작 + 1 + 카운트).atMost(경계)
347 let 아이디들 = map((시작 + 1)..<끝, i => toInt(칸들[i]) ?? -1)
348 { 목록: 아이디들.filter(아이디 => 아이디 >= 0 && 아이디 <= 100000), 다음: 끝 }
351// 진화 블록 11 칸이 다 있을 때만 읽습니다.
352function 진화블록파싱(칸들: Array<string>, 시작: int) -> 진화상태 {
353 if 시작 < 0 || 시작 + 10 >= 칸들.length { return 빈진화() }
354 {
355 단계: 진화단계파싱(칸들[시작]),
356 부모형태: 칸들[시작 + 1],
357 부모게놈: 게놈역직렬(칸들[시작 + 2]),
358 부모계통시드: 칸들[시작 + 3],
359 부모진화카운터: creature.진화카운터가두기(toInt(칸들[시작 + 4]) ?? 0),
360 부모세대: creature.세대가두기(toInt(칸들[시작 + 5]) ?? 0),
361 부모고유번호: toInt(칸들[시작 + 6]) ?? 0,
362 선택커서: toInt(칸들[시작 + 7]) ?? 0,
363 잠긴분기: toInt(칸들[시작 + 8]) ?? 0,
364 결과형태: 칸들[시작 + 9],
365 결과게놈: 게놈역직렬(칸들[시작 + 10]),
366 }
369function 칸정수(칸들: Array<string>, 위치: int) -> int {
370 if 위치 >= 0 && 위치 < 칸들.length { toInt(칸들[위치]) ?? 0 } else { 0 }
373// 파티커서 뒤의 진행 구획입니다. 진화 11 칸과 배틀 9 칸은 끝에 고정되고 계통 노드는 진화 블록 앞까지만 읽습니다.
374type 진행구획 = {
375 도감커서: int,
376 처치된것들: Array<int>,
377 초점: int,
378 스토리: 스토리상태,
379 계통노드들: Array<evolution.계통노드>,
380 진화: 진화상태,
383function 계통상한(진화시작: int, 계통시작: int, 배틀끝: int) -> int {
384 if 진화시작 >= 계통시작 { 진화시작 } else { 배틀끝 }
387// v6 은 도감커서, 처치 목록, 계통이 차례로 오고 v7 은 처치 목록 뒤에 초점, v8 은 그 뒤에 "본것:대기" 를 더합니다.
388function 진행구획v6(칸들: Array<string>, 버전: int, 커서위치: int, 진화시작: int) -> 진행구획 {
389 let 처치결과 = 처치파싱(칸들, 커서위치 + 2, 진화시작)
390 let 스토리위치 = 처치결과.다음 + 1
391 let 스토리조각들 = if 버전 >= 8 && 스토리위치 >= 0 && 스토리위치 < 칸들.length { 칸들[스토리위치].split(":") } else { [] }
392 let 스토리값 = if 스토리조각들.length >= 2 { { 본것: 비트가두기(toInt(스토리조각들[0]) ?? 0), 대기: 비트가두기(toInt(스토리조각들[1]) ?? 0) } } else { { 본것: 0, 대기: 0 } }
393 let 계통시작 = if 버전 >= 8 { 처치결과.다음 + 2 } else if 버전 >= 7 { 처치결과.다음 + 1 } else { 처치결과.다음 }
394 {
395 도감커서: 칸정수(칸들, 커서위치 + 1).atLeast(0),
396 처치된것들: 처치결과.목록,
397 초점: if 버전 >= 7 { 칸정수(칸들, 처치결과.다음) } else { 0 },
398 스토리: 스토리값,
399 계통노드들: 노드배열파싱(칸들, 계통시작, 계통상한(진화시작, 계통시작, 칸들.length)).목록,
400 진화: 진화블록파싱(칸들, 진화시작),
401 }
404// 이전 v5 는 도감커서 없이 파티커서 뒤에 바로 계통 카운트가 옵니다.
405function 진행구획v5(칸들: Array<string>, 커서위치: int, 진화시작: int) -> 진행구획 {
406 let 도감있음 = 커서위치 + 1 < 칸들.length && 계통섹션끝일치(칸들, 커서위치 + 2, 진화시작)
407 let 계통시작 = if 도감있음 { 커서위치 + 2 } else { 커서위치 + 1 }
408 {
409 도감커서: if 도감있음 { 칸정수(칸들, 커서위치 + 1).atLeast(0) } else { 0 },
410 처치된것들: [],
411 초점: 0,
412 스토리: { 본것: 0, 대기: 0 },
413 계통노드들: 노드배열파싱(칸들, 계통시작, 계통상한(진화시작, 계통시작, 칸들.length)).목록,
414 진화: 진화블록파싱(칸들, 진화시작),
415 }
418function 빈진행구획() -> 진행구획 {
419 { 도감커서: 0, 처치된것들: [], 초점: 0, 스토리: { 본것: 0, 대기: 0 }, 계통노드들: [], 진화: 빈진화() }
422type 본문 = {
423 스포어볼: int,
424 포획카운터: int,
425 파티: Array<creature.전투원>,
426 보관함: Array<creature.전투원>,
427 파티커서: int,
428 진행: 진행구획,
431// v3 부터는 가변 길이 파티와 보관함이 오고, v2 는 [14..17] 의 한 마리 파티, v1 과 v0 은 파티가 없어 시작종을 줍니다.
432function 본문파싱(칸들: Array<string>, 버전: int, 시드: string, 배틀있음: bool) -> 본문 {
433 if 버전 >= 3 && 칸들.length >= 17 {
434 let 보폭 = if 버전 >= 5 { 11 } else if 버전 >= 4 { 6 } else { 4 }
435 let 파티결과 = 배열파싱(칸들, 16, 1, 보폭, 시드, "party")
436 let 보관결과 = 배열파싱(칸들, 파티결과.다음, 0, 보폭, 시드, "box")
437 // 파티커서는 보관함 바로 뒤를 앞에서부터 셉니다. v5 이후의 계통 구획이 가변 길이라 끝에서 셀 수 없습니다.
438 let 커서위치 = 보관결과.다음
439 let 진화시작 = if 배틀있음 { 칸들.length - 9 - 11 } else { 칸들.length - 11 }
440 let 진행 = if 버전 >= 6 { 진행구획v6(칸들, 버전, 커서위치, 진화시작) } else if 버전 >= 5 { 진행구획v5(칸들, 커서위치, 진화시작) } else { 빈진행구획() }
441 return {
442 스포어볼: (toInt(칸들[14]) ?? 시작스포어볼()).atLeast(0),
443 포획카운터: 카운터가두기(toInt(칸들[15]) ?? 0),
444 파티: 파티결과.목록,
445 보관함: 보관결과.목록,
446 파티커서: 범위가두기(칸정수(칸들, 커서위치), 0, 파티결과.목록.length - 1),
447 진행: 진행,
448 }
449 }
450 let 파티 = if 칸들.length >= 18 { [전투원파싱(칸들, 14, 4, "{시드}#legacy:party:0")] } else { [creature.종에서전투원(species.시작종아이디(), 5, genome.시작계통시드(시드))] }
451 { 스포어볼: 시작스포어볼(), 포획카운터: 0, 파티: 파티, 보관함: [], 파티커서: 0, 진행: 빈진행구획() }
454// 적 레벨 0 은 야생 없음이고 99 로 가둬 피해 계산이 넘치지 않게 합니다.
455function 야생파싱(칸들: Array<string>) -> 야생크리처 {
456 if 칸들.length < 14 { return 빈야생() }
457 야생크리처 { 종아이디: toInt(칸들[8]) ?? 0, 이름: 칸들[13], 레벨: (toInt(칸들[9]) ?? 0).atLeast(0).atMost(99), 체력: 카운터가두기(toInt(칸들[10]) ?? 0), 스프라이트: toInt(칸들[11]) ?? 0, 속성: toInt(칸들[12]) ?? 0 }
460// 배틀은 v2 이후 늘 끝 9 칸입니다.
461function 배틀파싱(칸들: Array<string>) -> battle.배틀상태 {
462 if 칸들.length < 23 { return battle.빈배틀() }
463 let 시작 = 칸들.length - 9
464 {
465 단계: battle.단계파싱(칸들[시작]),
466 메뉴커서: toInt(칸들[시작 + 1]) ?? 0,
467 기술커서: toInt(칸들[시작 + 2]) ?? 0,
468 전투카운터: 카운터가두기(toInt(칸들[시작 + 3]) ?? 0),
469 적체력: 카운터가두기(toInt(칸들[시작 + 4]) ?? 0),
470 메시지커서: toInt(칸들[시작 + 5]) ?? 0,
471 다음단계: battle.다음단계파싱(칸들[시작 + 6]),
472 결과: battle.결과파싱(칸들[시작 + 7]),
473 메시지: 칸들[시작 + 8],
474 }
477export type 파싱오류 = "empty" | "short"
479// 계통 구획이 없던 옛 저장은 파티와 보관함의 원형 노드로 도감의 뿌리를 채웁니다.
480// 복원한 상태는 늘 v8 이라 옮겨 온 옛 저장도 v8 머리와 v8 본문으로 다시 저장됩니다.
481export function 역직렬화(문자열: string) -> Result<상태, 파싱오류> {
482 let 다듬은문자열 = 문자열.trim()
483 if 다듬은문자열 == "" { return Err("empty") }
484 let 칸들 = 다듬은문자열.split("|")
485 if 칸들.length < 7 { return Err("short") }
486 let 버전 = 버전파싱(칸들[0])
487 let 시드 = 칸들[1]
488 let 장면값: 장면 = if 칸들.length >= 8 { 장면파싱(칸들[7]) } else { "field" }
489 let 야생 = 야생파싱(칸들)
490 let 배틀값 = 배틀파싱(칸들)
491 let 읽은본문 = 본문파싱(칸들, 버전, 시드, 칸들.length >= 23)
492 let 진행 = 읽은본문.진행
493 let 스텝값 = 카운터가두기(toInt(칸들[2]) ?? 0)
494 let 노드들 = if 진행.계통노드들.length == 0 {
495 [for 묶음 in [읽은본문.파티, 읽은본문.보관함] for 원 in 묶음 => 시작노드(원, 스텝값)]
496 } else {
497 진행.계통노드들
498 }
499 Ok({
500 버전: 8,
501 월드시드: 시드,
502 스텝: 스텝값,
503 플레이어: {
504 x: 좌표가두기(toInt(칸들[3]) ?? 3),
505 y: 좌표가두기(toInt(칸들[4]) ?? 5),
506 방향: 방향읽기(toInt(칸들[5]) ?? 0),
507 },
508 맵아이디: 칸들[6],
509 장면: 장면값,
510 야생: 야생,
511 스포어볼: 읽은본문.스포어볼,
512 포획카운터: 읽은본문.포획카운터,
513 파티: 읽은본문.파티,
514 보관함: 읽은본문.보관함,
515 파티커서: 읽은본문.파티커서,
516 도감커서: if 노드들.length > 0 { 범위가두기(진행.도감커서, 0, 노드들.length - 1) } else { 0 },
517 처치된것들: 진행.처치된것들,
518 초점: 범위가두기(진행.초점, 0, 읽은본문.파티.length - 1),
519 스토리: 진행.스토리,
520 계통노드들: 노드들,
521 진화: 진행.진화,
522 배틀: 배틀값,
523 })

SHA256 cd2b5198ea6ed56621bb44f7d664cd6322145dc7df14257435d3c27d35dbc60a