Skip to content
This repository was archived by the owner on Aug 15, 2024. It is now read-only.

Commit 2542cb8

Browse files
committed
Initial commit
1 parent 8ef3f70 commit 2542cb8

30 files changed

+4990
-1
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
GNU GENERAL PUBLIC LICENSE
1+
GNU GENERAL PUBLIC LICENSE
22
Version 3, 29 June 2007
33

44
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
# SotN-RandomStartRoom
3+
<p align="center">
4+
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/License-GPLv3-blue.svg?style=flat-square&logo=arduino"></a>
5+
<a href="https://en.wikipedia.org/wiki/Prototype"><img alt="Prototype" src="https://img.shields.io/badge/Statut-Prototype-important.svg?style=flat-square&logo=arduino&logoColor=orange"></a>
6+
<a href="https://github.com/PatFrost/SotN-RandomStartRoom/pulls"><img alt="PRs Welcome" src="https://img.shields.io/badge/PRs-Welcome-brightgreen.svg?style=flat-square&logo=arduino&logoColor=brightgreen"></a>
7+
<a href="https://github.com/PatFrost/SotN-RandomStartRoom/issues"><img alt="Issues" src="https://img.shields.io/badge/Suggest-Open%20Issue-brightgreen.svg?style=flat-square&logo=arduino&logoColor=brightgreen"></a>
8+
<a href="https://github.com/PatFrost/SotN-RandomStartRoom/issues"><img alt="Issues" src="https://img.shields.io/badge/Bugs-Issues-red.svg?style=flat-square&logo=arduino&logoColor=red"></a>
9+
</p>
10+
11+
SotN-RandomStartRoom is tool for Castlevania: Symphony of the Night
12+
running under [BizHawk](https://tasvideos.org/BizHawk)
13+
14+
## Features!
15+
- Random start room.
16+
- Experimental: Randomize start equipements.
17+
- Richter Vs. Dracula (Final Boss)
18+
- Richter Vs. Richter
19+
20+
## Requirements
21+
- [Castlevania: Symphony of the Night (SLUS-00067) NTSC-U](http://redump.org/disc/3379/)
22+
- [BizHawk 2.7 or higher](https://github.com/TASEmulators/BizHawk/releases).
23+
- Windows users must download and run the [prereq installer](https://github.com/TASEmulators/BizHawk-Prereqs/releases) first.
24+
-
25+
- **Optional**, but add cool stuff and increase speed to RandomStartRoom
26+
- [Python 3.10 or higher](https://www.python.org/downloads/) and make sure you select [Add Python to PATH](https://docs.python.org/3/using/windows.html#installation-steps)
27+
- [psutil 5.9.0 or higher](https://pypi.org/project/psutil/)
28+
- [pywin32 303 or higher](https://pypi.org/project/pywin32/)
29+
30+
## Installation
31+
Download [SotN-RandomStartRoom-#.zip](https://github.com/PatFrost/SotN-RandomStartRoom/archive/refs/heads/main.zip) and extract all to your BizHawk folder.
32+
File structure should look like this:
33+
```
34+
BizHawk
35+
└───Lua
36+
│ └───PSX
37+
│ │ └───SotN-RandomStartRoom
38+
│ │ │ - RandomStartRoom.lua <- is main script
39+
│ │ │ - LICENSE
40+
│ │ │ - README.md
41+
│ │ │ - resources
42+
│ │ │ └───...
43+
```
44+
45+
## How to use RandomStartRoom
46+
1. Start Symphony of the Night under [BizHawk](https://tasvideos.org/BizHawk).
47+
2. If you used [SotnRandoTools](https://github.com/TalicZealot/SotnRandoTools) start this tool before RandomStartRoom.lua
48+
3. Open Lua console under BizHawk and open/run RandomStartRoom.lua
49+
4. On game Symphony of the Night go to gamesave.
50+
5. On dialog window of SotN-RandomStartRoom press "OK" button.
51+
6. Now start new game.
52+
53+
## Recommended cool tools
54+
- [SotnRandoTools](https://github.com/TalicZealot/SotnRandoTools)
55+
- [SotN Randomizer](https://sotn.io)
56+
57+
**Cheers!!!**
58+
59+
60+
<p align="center">
61+
<img alt="In Enter Olrox's Quarters" src="./resources/previews/preview1.jpg">
62+
<img alt="Richter Vs. Dracula" src="./resources/previews/preview2.jpg">
63+
</p>

RandomStartRoom.lua

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
-- Written by Frost
2+
3+
4+
console.clear()
5+
6+
package.loaded["./resources/libs/sotn"] = nil
7+
SOTN = require("./resources/libs/sotn")
8+
9+
10+
local TITLE = "SotN-RandomStartRoom - v1.0.0"
11+
UTILS.TITLE = TITLE
12+
13+
HAS_ROM_HASH = gameinfo.getromhash() ~= ""
14+
if not HAS_ROM_HASH then
15+
UTILS.message(TITLE, 'Start Castlevania - SotN first!', 'ok', 'warning')
16+
elseif mainmemory.readbyte(0x03C734) == 2 then
17+
UTILS.message(TITLE, 'Stop your game first!', 'ok', 'warning')
18+
HAS_ROM_HASH = false
19+
end
20+
21+
22+
UTILS.pause()
23+
24+
local room = ROOMS.getRoom()
25+
local iColor = tonumber
26+
local dialog = {}
27+
-- print(JSON:encode_pretty(room))
28+
-- print(JSON:encode_pretty(UTILS.settings))
29+
30+
31+
local function setMap(reset)
32+
local w = room.map.w or 15
33+
local h = room.map.h or 15
34+
local x = w * (room.map.x+2)
35+
local y = h * room.map.y
36+
local scale = 1.25
37+
local size = 11/scale
38+
x = (x - scale)/scale
39+
y = (y - scale)/scale
40+
41+
if reset and dialog.group then
42+
forms.clear(dialog.group, iColor(UTILS.settings.color.Clear))
43+
end
44+
dialog.group = forms.pictureBox(dialog.bg, 0, 20, 986/scale, 731/scale)
45+
forms.drawRectangle(dialog.group, 0, 0, 986/scale, 731/scale, iColor(UTILS.settings.color.Black), iColor(UTILS.settings.color.Black))
46+
local image
47+
if room.vs == 0x38 then
48+
image = UTILS.settings.images.rvsd
49+
elseif room.vs == 0x18 then
50+
image = UTILS.settings.images.rvsr
51+
elseif not room.map.inv then
52+
image = UTILS.settings.images.map1
53+
elseif room.map.inv == 2 then
54+
image = UTILS.settings.images.map2
55+
elseif room.map.inv == 3 then
56+
image = UTILS.settings.images.map3
57+
end
58+
if image then
59+
forms.drawImage(dialog.group, image, size, size, 964/scale, 709/scale, true)
60+
end
61+
62+
if not room.vs then
63+
forms.drawEllipse(forms.pictureBox(dialog.group, x, y, size, size),
64+
x, y, size, size, iColor(UTILS.settings.color.Black), iColor(UTILS.settings.pointer.color))
65+
end
66+
67+
forms.settext(dialog.descList, string.format("%d. %s", room.pos+1, room.desc))
68+
forms.refresh(dialog.group)
69+
end
70+
71+
local eventtime = os.clock()
72+
local function eventLists()
73+
local selected = forms.gettext(dialog.colorList)
74+
if UTILS.settings.color[selected] ~= UTILS.settings.pointer.color then
75+
UTILS.settings.pointer.color = UTILS.settings.color[selected]
76+
setMap()
77+
end
78+
79+
if os.clock() - eventtime > 0.5 then
80+
selected = forms.gettext(dialog.descList)
81+
if ROOMS.rooms[ROOMS.descToIndex[selected]] ~= room then
82+
room = ROOMS.rooms[ROOMS.descToIndex[selected]]
83+
setMap()
84+
end
85+
eventtime = os.clock()
86+
end
87+
end
88+
89+
local function showDialog()
90+
forms.destroyall()
91+
dialog.bg = forms.newform(UTILS.settings.coords.w, UTILS.settings.coords.h,
92+
TITLE, function(x) dialog.cancel=true end)
93+
UTILS.centerWindow(dialog.bg)
94+
95+
dialog.descList = forms.dropdown(dialog.bg, {room.desc}, 0, 0, 590, 794)
96+
forms.setdropdownitems(dialog.descList, ROOMS.droplist, false)
97+
-- forms.addclick(dialog.descList, function(x) eventLists() end)
98+
99+
local space = 30
100+
local posx, posy = 626, 20
101+
forms.button(dialog.bg, "RANDOM ROOM", function(x)
102+
room = ROOMS.getRoom()
103+
setMap(true)
104+
end, posx, posy, 145, 25)
105+
forms.button(dialog.bg, "RICHTER VS. DRACULA", function(x)
106+
room = ROOMS.getRoom(0x38)
107+
room.vs = 0x38
108+
setMap(true)
109+
end, posx, posy+space, 145, 25)
110+
forms.button(dialog.bg, "RICHTER VS. RICHTER", function(x)
111+
room = ROOMS.getRoom(0x18)
112+
room.vs = 0x18
113+
setMap(true)
114+
end, posx, posy+(space*2), 145, 25)
115+
forms.button(dialog.bg, "OK", function(x)
116+
if mainmemory.readbyte(0x03C734) == 8 then
117+
dialog.okClicked = true
118+
forms.destroy(dialog.bg)
119+
else
120+
UTILS.message(TITLE, forms.gettext(dialog.statusLBL))
121+
end
122+
end, posx, posy+(space*3), 145, 25)
123+
124+
-- Info game
125+
posx, posy = 596, 145
126+
forms.label(dialog.bg, "GAME", posx+87, posy, 64, 15)
127+
forms.pictureBox(dialog.bg, posx, posy+15, 270, 1) -- used as seperator
128+
forms.label(dialog.bg, gameinfo.getromname(), posx, posy+20, 200, 15)
129+
forms.label(dialog.bg, "Hash: "..gameinfo.getromhash(), posx, posy+35, 200, 15)
130+
forms.label(dialog.bg, "Seed :\nPreset :\nVersion :", posx, posy+48, 45, 45)
131+
dialog.gInfo = forms.label(dialog.bg, SOTN.getSeed(), posx+42, posy+48, 155, 45)
132+
133+
-- Info status
134+
posy = 240
135+
forms.label(dialog.bg, "STATUS", posx+80.5, posy, 64, 15)
136+
forms.pictureBox(dialog.bg, posx, posy+15, 270, 1) -- used as seperator
137+
dialog.statusLBL = forms.label(dialog.bg, "Busy", posx, posy+20, 200, 155)
138+
139+
-- Options
140+
posy = 415
141+
forms.label(dialog.bg, "OPTIONS", posx+79, posy, 64, 15)
142+
forms.pictureBox(dialog.bg, posx, posy+15, 270, 1) -- used as seperator
143+
144+
forms.label(dialog.bg, "Room color:", posx, posy+29, 64, 15)
145+
local list = {}
146+
for name, i in pairs(UTILS.settings.color) do table.insert(list, name) end
147+
dialog.colorList = forms.dropdown(dialog.bg, list, posx+65, posy+25, 135, 100)
148+
forms.settext(dialog.colorList , "Green")
149+
150+
forms.pictureBox(dialog.bg, posx, 470, 270, 1) -- used as seperator
151+
forms.label(dialog.bg, "Powered by Frost", 716, 475, 100, 15)
152+
153+
setMap()
154+
end
155+
156+
local oldstatus
157+
local function setStatusLabel()
158+
if dialog.statusLBL then
159+
local st = mainmemory.readbyte(0x03C734)
160+
if st ~= oldstatus then
161+
-- print("new status: "..st)
162+
oldstatus = st
163+
local text = "Busy"
164+
if st == 1 then
165+
text = 'On game "Press Start Button",\nGo to in "File Select".\nAnd on this window press "OK" button.'
166+
elseif st == 8 then
167+
text = 'Press "OK" button, wait 3 secondes and Start New Game.'
168+
end
169+
170+
if st == 1 or st == 8 then
171+
cut1, cut2 = SOTN.getCutScene()
172+
if cut1 and cut1 ~= 26 then
173+
text = text .. '\n\nRemenber,\nYour game version is not fully compatible with this tool. Use original game or use this code "800A2988 001A" by default this tool enable this code, but make sure this code enabled all times with this tool.'
174+
end
175+
end
176+
forms.settext(dialog.statusLBL, text)
177+
forms.refresh(dialog.statusLBL)
178+
end
179+
end
180+
end
181+
182+
183+
local addEquip = true
184+
local cheatAdded = 0
185+
local stime = 0 -- time start entering In Select your Destiny or In File Select
186+
local sleep = 3 -- time to sleep in secs before activate cheat
187+
function checkStartGame()
188+
local status = mainmemory.readbyte(0x03C734)
189+
if addEquip and status == 4 then
190+
SOTN.setEquip(room)
191+
elseif status == 2 then
192+
-- In Game
193+
if cheatAdded == 1 then
194+
client.removecheat('D00974A0 00' .. bizstring.hex(room.pos))
195+
client.removecheat('8003C9A0 0001') -- desable richter
196+
cheatAdded = 2
197+
end
198+
elseif status == 8 then
199+
-- In Select your Destiny or In File Select
200+
if stime == 0 then
201+
stime = os.time() + sleep
202+
end
203+
if cheatAdded == 0 and os.time() > stime then
204+
client.removecheat('8003C9A0 0001') -- desable richter
205+
if room.vs == 0x18 or room.vs == 0x38 then
206+
client.addcheat('8003C9A0 0001') -- enable richter
207+
print("Cheat: Force play as Richter")
208+
end
209+
client.addcheat('8003CAFC 0000') -- Stereo On
210+
print("Cheat: Stereo On")
211+
client.addcheat('D00974A0 00' .. bizstring.hex(room.pos)) -- set start room
212+
cut1, cut2 = SOTN.getCutScene()
213+
if cut1 and cut1 ~= 26 then
214+
client.addcheat('800A2988 001A') -- Remove Maria cut scene patched by SotN-Randomizer
215+
print("Cheat: Maria uncut scene")
216+
end
217+
cheatAdded = 1
218+
end
219+
end
220+
end
221+
222+
event.onexit(function(x)
223+
UTILS.saveSettings(UTILS.settings)
224+
forms.destroyall()
225+
UTILS.destroymsgid()
226+
UTILS.unpause()
227+
client.removecheat('D00974A0 0000')
228+
client.removecheat('8003C9A0 0001') -- desable richter
229+
end)
230+
231+
if not HAS_ROM_HASH or mainmemory.readbyte(0x03C734) ~= 2 then
232+
client.removecheat('800A2988 001A')
233+
client.removecheat('D00974A0 0000')
234+
client.removecheat('8003C9A0 0001') -- desable richter
235+
client.removecheat('8003CAFC 0000') -- Stereo On
236+
client.removecheat('8003C4E6 BCAA') -- Save Palette
237+
end
238+
239+
if HAS_ROM_HASH then
240+
UTILS.showHawk()
241+
showDialog()
242+
end
243+
244+
UTILS.unpause()
245+
246+
local hasinfo
247+
while HAS_ROM_HASH do
248+
if dialog.okClicked or dialog.cancel then
249+
break -- End of script
250+
else
251+
if not hasinfo then
252+
local gi = forms.gettext(dialog.gInfo)
253+
if not gi or gi == "" then
254+
forms.settext(dialog.gInfo, SOTN.getSeed())
255+
else
256+
hasinfo = true
257+
end
258+
end
259+
setStatusLabel()
260+
eventLists()
261+
end
262+
emu.yield()
263+
end
264+
265+
if dialog.okClicked then
266+
addEquip = room.pos ~= 0x1F and room.pos ~= 0X3F and room.pos ~= 0x41
267+
while true do
268+
if cheatAdded == 2 then
269+
if addEquip then
270+
SOTN.setupGame(room)
271+
end
272+
print(JSON:encode_pretty(room))
273+
break -- End of script
274+
end
275+
checkStartGame()
276+
emu.frameadvance()
277+
end
278+
end

resources/default_settings.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"coords": {
3+
"x": 204,
4+
"y": 160,
5+
"w": 818,
6+
"h": 533
7+
},
8+
"pointer": {
9+
"color": "0xFF00FF00"
10+
},
11+
"color": {
12+
"Black": "0xFF000000",
13+
"Blue": "0xFF007FFF",
14+
"Clear": "0x00000000",
15+
"Cyan": "0xFF00FFFF",
16+
"Gray": "0xFF3F3F3F",
17+
"Green": "0xFF00FF00",
18+
"Lightblue": "0xFF7CCCF8",
19+
"Orange": "0xFFFF7F00",
20+
"Pink": "0xFFFF7FFF",
21+
"Purple": "0xFFFF00FF",
22+
"Red": "0xFFFF0000",
23+
"White": "0xFFFFFFFF",
24+
"Yellow": "0xFFFFFF00"
25+
},
26+
"images": {
27+
"map1": "./resources/images/map1.png",
28+
"map2": "./resources/images/map2.png",
29+
"map3": "./resources/images/map3.png",
30+
"rvsd": "./resources/images/rvsd.png",
31+
"rvsr": "./resources/images/rvsr.png"
32+
},
33+
"lastroom": 0
34+
}

resources/images/alucard.ico

4.19 KB
Binary file not shown.

resources/images/alucard.png

14.7 KB
Loading

resources/images/map1.png

30.6 KB
Loading

resources/images/map2.png

27 KB
Loading

resources/images/map3.png

26 KB
Loading

resources/images/maria.ico

103 KB
Binary file not shown.

0 commit comments

Comments
 (0)