Skip to content
This repository was archived by the owner on Jul 18, 2019. It is now read-only.

Commit 5b6e506

Browse files
committed
GH-16 support for uploading custom levels
1 parent 2e021a4 commit 5b6e506

File tree

6 files changed

+145
-15
lines changed

6 files changed

+145
-15
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This project uses Python <b>3.7</b> and the following dependencies:
1616
- AutoBahn
1717
- Emoji
1818
- ConfigParser
19+
- jsonschema
1920

2021
<b>If you are on Windows, the module <u>pypiwin32</u> may also be required.</b>
2122

levelSchema.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{ "type" : "object",
2+
"properties" : {
3+
"type" : {"type" : "string", "pattern" : "^game$"},
4+
"resource" : {
5+
"type" : "array",
6+
"items" : {
7+
"type" : "object",
8+
"properties" : {
9+
"id" : {"type":"string", "minLength" : 1},
10+
"src" : {"type":"string", "minLength" : 1}
11+
},
12+
"required" : ["id", "src"]
13+
}
14+
},
15+
"initial" : { "type" : "integer" },
16+
"world" : {
17+
"type" : "array",
18+
"minItems" : 1,
19+
"items" : {
20+
"type" : "object",
21+
"properties" : {
22+
"id" : { "type" : "integer" },
23+
"name" : { "type" : "string", "minLength" : 1},
24+
"initial" : { "type" : "integer" },
25+
"zone" : {
26+
"type" : "array",
27+
"minItems" : 1,
28+
"items" : {
29+
"type": "object",
30+
"properties" : {
31+
"id" : { "type" : "integer" },
32+
"initial" : { "type" : "integer" },
33+
"color" : { "type" : "string", "pattern" : "#[0-9a-fA-F]{6}" },
34+
"music" : { "type" : "string", "minLength" : 1},
35+
"data" : {
36+
"type" : "array",
37+
"minItems" : 1,
38+
"items" : {
39+
"type" : "array",
40+
"minItems" : 1,
41+
"items" : {"type" : "integer"}
42+
}
43+
},
44+
"obj" : {
45+
"type" : "array",
46+
"items" : {
47+
"type" : "object",
48+
"properties" : {
49+
"type" : {"type" : "integer"},
50+
"pos" : {"type" : "integer"},
51+
"param" : {"type" : "array"}
52+
},
53+
"required" : ["type", "pos", "param"]
54+
}
55+
},
56+
"warp" : {
57+
"type" : "array",
58+
"items" : {
59+
"type" : "object",
60+
"properties" : {
61+
"id" : {"type" : "integer"},
62+
"pos" : {"type" : "integer"},
63+
"data" : {"type" : "integer"}
64+
},
65+
"required" : ["id", "pos", "data"]
66+
}
67+
}
68+
},
69+
"required" : ["id", "initial", "color", "music", "data", "obj", "warp"]
70+
}
71+
}
72+
},
73+
"required": ["id", "name", "initial", "zone"]
74+
}
75+
}
76+
},
77+
"required": ["type", "resource", "initial", "world"]
78+
}

match.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
from twisted.internet import reactor
22
from buffer import Buffer
33
import random
4+
import json
5+
import os
6+
import jsonschema
7+
8+
autoMatchStartTime = 30
9+
10+
levelJsonSchema = json.loads(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "levelSchema.json"), "r").read())
11+
level = json.loads(open("D:\\Projects\\github\\MarioRoyale\\game\\world-1.json").read())
12+
#level = json.loads(open("D:\\Projects\\github\\MarioRoyale\\game\\lobby.json").read())
13+
jsonschema.validate(instance=level, schema=levelJsonSchema)
414

515
class Match(object):
616
def __init__(self, server, roomName, private):
717
self.server = server
818

19+
self.forceLevel = ""
20+
self.customLevelData = ""
921
self.world = "lobby"
1022
self.roomName = roomName
1123
self.closed = False
@@ -81,7 +93,7 @@ def broadBin(self, code, buff, ignore = None):
8193

8294
def broadLoadWorld(self):
8395
for player in self.players:
84-
player.loadWorld(self.world)
96+
player.loadWorld(self.world, self.customLevelData)
8597

8698
def broadStartTimer(self, time):
8799
self.startTimer = time * 30
@@ -114,12 +126,13 @@ def getPlayersData(self):
114126
return playersData
115127

116128
def onPlayerReady(self, player):
117-
if not self.playing: # Ensure that the game starts even with fewer players
118-
try:
119-
self.autoStartTimer.cancel()
120-
except:
121-
pass
122-
self.autoStartTimer = reactor.callLater(30, self.start, True)
129+
if not self.private:
130+
if not self.playing: # Ensure that the game starts even with fewer players
131+
try:
132+
self.autoStartTimer.cancel()
133+
except:
134+
pass
135+
self.autoStartTimer = reactor.callLater(autoMatchStartTime, self.start, True)
123136

124137
if self.world == "lobby" and self.goldFlowerTaken:
125138
self.broadBin(0x20, Buffer().writeInt16(-1).writeInt8(0).writeInt8(0).writeInt32(458761).writeInt8(0))
@@ -161,8 +174,31 @@ def start(self, forced = False):
161174
except:
162175
pass
163176

164-
self.world = random.choice(self.server.worlds)
177+
self.world = self.forceLevel if self.forceLevel != "" else random.choice(self.server.worlds)
165178
self.broadLoadWorld()
166179

167180
reactor.callLater(1, self.broadStartTimer, self.server.startTimer)
168-
181+
182+
def validateCustomLevel(self, level):
183+
lk = json.loads(level)
184+
jsonschema.validate(instance=lk, schema=levelJsonSchema)
185+
186+
def selectLevel(self, level):
187+
if not self.private:
188+
return
189+
if level == "" or level in self.server.worlds:
190+
self.forceLevel = level
191+
self.broadLevelSelect()
192+
193+
def broadLevelSelect(self):
194+
for player in self.players:
195+
player.sendJSON({"type":"gsl", "name":self.forceLevel, "status":"update", "message":""})
196+
197+
198+
def selectCustomLevel(self, level):
199+
if not self.private:
200+
return
201+
self.validateCustomLevel(level)
202+
self.forceLevel = "custom"
203+
self.customLevelData = level
204+
self.broadLevelSelect()

player.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@ def getSimpleData(self):
4545
def serializePlayerObject(self):
4646
return Buffer().writeInt16(self.id).writeInt8(self.level).writeInt8(self.zone).writeShor2(self.posX, self.posY).writeInt16(self.skin).toBytes()
4747

48-
def loadWorld(self, worldName):
48+
def loadWorld(self, worldName, levelData):
4949
self.dead = True
5050
self.loaded = False
5151
self.pendingWorld = worldName
52-
self.sendJSON({"packets": [
53-
{"game": worldName, "type": "g01"}
54-
], "type": "s01"})
52+
msg = {"game": worldName, "type": "g01"}
53+
if worldName == "custom":
54+
msg["levelData"] = levelData
55+
self.sendJSON({"packets": [msg], "type": "s01"})
5556
self.client.startDCTimer(15)
5657

5758
def setStartTimer(self, time):
@@ -66,7 +67,7 @@ def onEnterIngame(self):
6667
if self.match.world == "lobby":
6768
self.lobbier = True
6869

69-
self.loadWorld(self.match.world)
70+
self.loadWorld(self.match.world, self.match.customLevelData)
7071

7172
def onLoadComplete(self):
7273
if self.loaded or self.pendingWorld is None:

server.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ MaxSimulIP: 3
1919

2020
[Match]
2121
# Minimum of players to a match start by votes
22-
PlayerMin: 2
22+
PlayerMin: 1
2323

2424
# Maximum of players in a match
2525
PlayerCap: 75

server.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ def onTextMessage(self, payload):
195195
elif type == "g51": # (SPECIAL) Force start
196196
if self.server.mcode and self.server.mcode in packet["code"]:
197197
self.player.match.start(True)
198+
elif type == "gsl": #level select
199+
if self.player is not None:
200+
levelName = packet["name"]
201+
if levelName == "custom":
202+
try:
203+
self.player.match.selectCustomLevel(packet["data"])
204+
except Exception as e:
205+
estr = str(e)
206+
estr = "\n".join(estr.split("\n")[:10])
207+
self.sendJSON({"type":"gsl", "name":levelName, "status":"error", "message":estr})
208+
return
209+
self.sendJSON({"type":"gsl", "name":levelName, "status":"success", "message":""})
210+
else:
211+
self.player.match.selectLevel(levelName)
198212

199213
def onBinaryMessage(self):
200214
pktLenDict = { 0x10: 6, 0x11: 0, 0x12: 12, 0x13: 1, 0x17: 2, 0x18: 4, 0x19: 0, 0x20: 7, 0x30: 7 }

0 commit comments

Comments
 (0)