e8845005 by Barry

Added image release tool to rebuild automatically and put the bin file

out.
1 parent c272edb4
...@@ -12,6 +12,14 @@ I like the NodeMcu devices. They have 4mb flash and work well and are fairly rob ...@@ -12,6 +12,14 @@ I like the NodeMcu devices. They have 4mb flash and work well and are fairly rob
12 12
13 ### How to Load on a New ESP8266 13 ### How to Load on a New ESP8266
14 14
15 Vagrant setup
16
17 https://learn.adafruit.com/building-and-running-micropython-on-the-esp8266/build-firmware
18
19 Setup Vagrant SCP
20
21 vagrant plugin install vagrant-scp
22
15 Use python 3.6+ 23 Use python 3.6+
16 24
17 Create a virtual environment to save your brainspace. 25 Create a virtual environment to save your brainspace.
...@@ -38,6 +46,14 @@ Install esptool and the other required pip modules which will let you install th ...@@ -38,6 +46,14 @@ Install esptool and the other required pip modules which will let you install th
38 pip install -r requirements.txt 46 pip install -r requirements.txt
39 ``` 47 ```
40 48
49 NEW
50 Custom Vagrant image:
51
52 ```
53 python release_image.py
54 ```
55
56 <!-- THIS IS REPLACED BY THE NEW RELEASE IMAGE SCRIPT
41 After the modules are installed you will need to use esptool to erase the flash. 57 After the modules are installed you will need to use esptool to erase the flash.
42 if things go wrong... https://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html 58 if things go wrong... https://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html
43 59
...@@ -60,6 +76,7 @@ After the file has been downloaded (use wget? browser? curl? whatever makes you ...@@ -60,6 +76,7 @@ After the file has been downloaded (use wget? browser? curl? whatever makes you
60 ``` 76 ```
61 esptool --port COM1 --baud 460800 write_flash --flash_size=detect 0 esp8266-20171101-v1.9.3.bin 77 esptool --port COM1 --baud 460800 write_flash --flash_size=detect 0 esp8266-20171101-v1.9.3.bin
62 ``` 78 ```
79 -->
63 80
64 This will write the binary micropython os to the device. After this you will want to validate the write was successful. 81 This will write the binary micropython os to the device. After this you will want to validate the write was successful.
65 82
......
1 #!/usr/bin/env python 1 import weemud
2
3 """
4 MudServer author: Mark Frimston - mfrimston@gmail.com
5
6 Micropython port and expansion author: Barry Ruffner - barryruffner@gmail.com
7 """
8
9 from time import sleep
10 from math import floor
11 from sys import platform
12 from os import listdir
13
14 from mudserver import MudServer
15 from commandhandler import CommandHandler
16 from utils import load_object_from_file, save_object_to_file, password_hash, calc_att, get_att
17
18 if 'esp' in platform:
19 from gc import collect, mem_free
20 from machine import Pin
21 # import the MUD server class
22
23
24 print('STARTING MUD\r\n\r\n\r\n')
25
26 # Setup button so when pressed the mud goes in to wifi hotspot mode on 192.168.4.1
27 if 'esp' in platform:
28 flash_button = Pin(0, Pin.IN, Pin.PULL_UP)
29
30 # stores the players in the game
31 players = {}
32
33 # start the server
34 globals()['mud'] = MudServer()
35
36
37 def show_prompt(pid):
38 if "prompt" not in players[pid]:
39 players[pid]["prompt"] = "> "
40 if 'hp' not in players[pid]:
41 players[pid]["hp"] = 100
42 if 'mp' not in players[pid]:
43 players[pid]["mp"] = 100
44 if 'sta' not in players[pid]:
45 players[pid]["sta"] = 10
46
47 prompt = players[pid]["prompt"].replace('%st', str(floor(players[pid]['sta']))).replace('%hp', str(floor(players[pid]["hp"]))).replace('%mp', str(floor(players[pid]["mp"])))
48 mud.send_message(pid, "\r\n" + prompt, '')
49
50 tick = 0.0
51 spawn = 0.0
52 cmd_handler = CommandHandler()
53
54 def spawn_mobs(players):
55 rooms = listdir('rooms')
56 for room in rooms:
57 if '_monsters.json' not in room:
58 continue
59 room_monsters = load_object_from_file('rooms/{}'.format(room))
60 for mon_name, monster in room_monsters.items():
61 monster_template = load_object_from_file('mobs/{}.json'.format(mon_name))
62 if not monster_template:
63 continue
64 while len(room_monsters[mon_name]['active']) < monster['max']:
65 print('Spawning {} in {}'.format(mon_name, room))
66 mp = get_att(monster_template['spawn']["mp"])
67 hp = get_att(monster_template['spawn']["hp"])
68 sta = get_att(monster_template['spawn']["sta"])
69 new_active = {
70 "mp": mp,
71 "maxhp": hp,
72 "hp": hp,
73 "sta": sta,
74 "maxmp": mp,
75 "target": "",
76 "action": "attack",
77 "maxsta": sta}
78 room_monsters[mon_name]['active'].append(new_active)
79 for pid, pl in players.items():
80 if players[pid]['room'].lower() == room.split('_')[0]:
81 mud.send_message(pid, "a {} arrived".format(mon_name))
82 save_object_to_file(room_monsters, 'rooms/{}'.format(room))
83
84 def run_mobs(players, mud):
85 for pid, player in players.items():
86 if not player or not player.get("name") or not player.get('password'):
87 continue
88 if player['mp'] < player['maxmp']:
89 players[pid]['mp'] += player['mpr']
90 if player['sta'] < player['maxsta']:
91 players[pid]['sta'] += player['star']
92 room_monsters = load_object_from_file('rooms/{}_monsters.json'.format(player['room']))
93 for mon_name, monster in room_monsters.items():
94 monster_template = load_object_from_file('mobs/{}.json'.format(mon_name))
95 for active_monster_idx, active_monster in enumerate(monster['active']):
96 sta = active_monster['sta']
97 if active_monster['mp'] < active_monster['maxmp']:
98 active_monster['mp'] += monster_template['mpr']
99 if sta < active_monster['maxsta']:
100 sta += monster_template['star']
101 active_monster['sta'] = sta
102 if active_monster['action'] == "attack" and active_monster['target'] == player['name']:
103 if player.get("weapon"):
104 weapon = load_object_from_file('inventory/{}.json'.format(player['weapon']))
105 att = get_att(weapon['damage'])
106 mud.send_message(pid, "Your %s strikes the %s for %d" % (weapon['title'], mon_name, att,), color='yellow')
107 else:
108 att = get_att(player['aa'])
109 mud.send_message(pid, "You hit the %s for %d" % (mon_name, att,), color='yellow')
110 active_monster['hp'] -= att
111 if active_monster['hp'] <= 0:
112 del room_monsters[mon_name]['active'][active_monster_idx]
113 mud.send_message(pid, "The %s dies." % (mon_name,), color=['bold', 'blue'])
114 break
115 if active_monster.get("weapon"):
116 weapon = load_object_from_file('inventory/{}.json'.format(active_monster['weapon']))
117 att = get_att(weapon['damage'])
118 mud.send_message(pid, "The %s strikes you with a %s for %d" % (mon_name, weapon['title'], att,), color='magenta')
119 else:
120 att = get_att(monster_template['aa'])
121 mud.send_message(pid, "You were hit by a %s for %d" % (mon_name, att,), color='magenta')
122 players[pid]['hp'] -= att
123 if (active_monster['hp']/active_monster['maxhp'] < 0.25) or (active_monster['mp'] > 0 and active_monster['mp']/active_monster['maxmp'] > 0.5) or (sta > 0 and sta/active_monster['maxsta'] > 0.5):
124 magic_cast = False
125 if active_monster['mp'] > 0:
126 att, active_monster['mp'] = calc_att(mud, pid, monster_template['sp'], active_monster['mp'])
127 players[pid]['hp'] -= att
128 if att > 0:
129 magic_cast = True
130 if not magic_cast:
131 if sta > 0:
132 att, sta = calc_att(mud, pid, monster_template['at'], sta)
133 active_monster['sta'] = sta
134 players[pid]['hp'] -= att
135
136 room_monsters[mon_name]['active'][active_monster_idx] = active_monster
137
138 save_object_to_file(room_monsters, 'rooms/{}_monsters.json'.format(player['room']))
139
140 # main game loop. We loop forever (i.e. until the program is terminated)
141 while True:
142 if 'esp' in platform:
143 collect()
144 if flash_button.value() == 0:
145 break
146 # pause for 1/5 of a second on each loop, so that we don't constantly
147 sleep(0.002)
148 if 'esp' in platform:
149 tick += 0.001
150 else:
151 tick += 0.0005
152 if spawn >= 10:
153 spawn = 0
154 try:
155 spawn_mobs(players)
156 except Exception as e:
157 print('spawner error:')
158 print(e)
159 if 'esp' in platform:
160 collect()
161 if tick >= 1:
162 if 'esp' in platform:
163 print(mem_free())
164 spawn += tick
165 tick = 0
166 try:
167 run_mobs(players, mud)
168 except Exception as e:
169 print('mob error:')
170 print(e)
171 # 'update' must be called in the loop to keep the game running and give
172 # us up-to-date information
173 mud.update()
174
175 # go through any newly connected players
176 for id in mud.get_new_players():
177
178 # add the new player to the dictionary, noting that they've not been
179 # named yet.
180 # The dictionary key is the player's id number. We set their room to
181 # None initially until they have entered a name
182 # Try adding more player stats - level, gold, inventory, etc
183 players[id] = load_object_from_file("defaultplayer.json")
184 with open('welcome.txt', 'r', encoding='utf-8') as f:
185 for line in f:
186 mud.send_message(id, line, "\r")
187 # send the new player a prompt for their name
188 #bytes_to_send = bytearray([mud._TN_INTERPRET_AS_COMMAND, mud._TN_WONT, mud._ECHO])
189 #mud.raw_send(id, bytes_to_send)
190 mud.send_message(id, "What is your name?")
191
192 # go through any recently disconnected players
193 for id in mud.get_disconnected_players():
194
195 # if for any reason the player isn't in the player map, skip them and
196 # move on to the next one
197 if id not in players:
198 continue
199
200 # go through all the players in the game
201 for pid, pl in players.items():
202 # send each player a message to tell them about the diconnected
203 # player
204 if players[pid].get("name") is not None:
205 mud.send_message(pid, "{} quit the game".format(players[pid]["name"]))
206
207 # remove the player's entry in the player dictionary
208 if players[id]["name"] is not None:
209 save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
210 del(players[id])
211
212 # go through any new commands sent from players
213 for id, command, params in mud.get_commands():
214
215 # if for any reason the player isn't in the player map, skip them and
216 # move on to the next one
217 if id not in players:
218 continue
219
220 # if the player hasn't given their name yet, use this first command as
221 # their name and move them to the starting room.
222 if players[id].get("name") is None:
223 if command.strip() == '' or len(command) > 12 or not command.isalnum() or command is None:
224 mud.send_message(id, "Invalid Name")
225 print("Bad guy!")
226 print(mud.get_remote_ip(id))
227 continue
228 already_logged_in = False
229 for pid, pl in players.items():
230 if players[pid].get("name") == command:
231 mud.send_message(id, "{} is already logged in.".format(command))
232 mud.disconnect_player(id)
233 continue
234 loaded_player = load_object_from_file("players/{}.json".format(command))
235 mud.remote_echo(id, False)
236 players[id]["name"] = command
237 if not loaded_player:
238 # Player does not exist.
239 mud.send_message(id, "Character does not exist. Please enter a password to create a new character: ")
240 else:
241 mud.send_message(id, "Enter Password: ")
242
243 elif players[id]["password"] is None:
244 if players[id].get("retries", 0) > 1:
245 mud.send_message(id, "Too many attempts")
246 mud.disconnect_player(id)
247 continue
248
249 if command.strip() == '' or command is None:
250 players[id]["retries"] = players[id].get("retries", 0) + 1
251 mud.send_message(id, "Invalid Password")
252 continue
253
254 loaded_player = load_object_from_file("players/{}.json".format(players[pid]["name"]))
255 if loaded_player is None:
256 players[id]["password"] = password_hash(players[pid]["name"], command)
257 players[id]["room"] = "town/tavern"
258 else:
259 if loaded_player["password"] == password_hash(players[pid]["name"], command):
260 players[id] = loaded_player
261 else:
262 players[id]["retries"] = players[id].get("retries", 0) + 1
263 mud.send_message(id, "Invalid Password")
264 continue
265
266 # go through all the players in the game
267 for pid, pl in players.items():
268 # send each player a message to tell them about the new player
269 mud.send_message(pid, "{} entered the game".format(players[id]["name"]))
270
271 mud.remote_echo(id, True)
272 # send the new player a welcome message
273 mud.send_message(id, "\r\n\r\nWelcome to the game, {}. ".format(players[id]["name"]) +
274 "\r\nType 'help' for a list of commands. Have fun!\r\n\r\n")
275
276 # send the new player the description of their current room
277
278 cmd_handler.parse(id, 'look', '', mud, players)
279 show_prompt(id)
280 else:
281 if 'esp' in platform:
282 collect()
283
284 cmd_handler.parse(id, command, params, mud, players)
285 show_prompt(id)
286
287 # Start WIFI Setup
288 if 'esp' in platform:
289 if flash_button.value() == 0:
290 print('Starting WIFIWeb')
291 import wifiweb
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,9 +12,13 @@ import socket ...@@ -12,9 +12,13 @@ import socket
12 import select 12 import select
13 import time 13 import time
14 import sys 14 import sys
15 import struct
16 import json 15 import json
17 16
17 if 'esp' in sys.platform:
18 import ustruct
19 else:
20 import struct
21
18 from utils import get_color, get_color_list, multiple_replace 22 from utils import get_color, get_color_list, multiple_replace
19 23
20 24
...@@ -426,7 +430,7 @@ class MudServer(object): ...@@ -426,7 +430,7 @@ class MudServer(object):
426 430
427 try: 431 try:
428 # read data from the socket, using a max length of 4096 432 # read data from the socket, using a max length of 4096
429 data = cl.socket.recv(4096) 433 data = cl.socket.recv(1024)
430 434
431 # process the data, stripping out any special Telnet commands 435 # process the data, stripping out any special Telnet commands
432 message = self._process_sent_data(cl, data) 436 message = self._process_sent_data(cl, data)
...@@ -640,6 +644,9 @@ class MudServer(object): ...@@ -640,6 +644,9 @@ class MudServer(object):
640 if option_state == self._READ_NAWS: 644 if option_state == self._READ_NAWS:
641 height = 30 645 height = 30
642 width = 100 646 width = 100
647 if 'esp' in sys.platform:
648 height, width = ustruct.unpack('>hh', option_data)
649 else:
643 height, width = struct.unpack('>hh', option_data) 650 height, width = struct.unpack('>hh', option_data)
644 if height > 0: 651 if height > 0:
645 client.height = height 652 client.height = height
......
...@@ -11,15 +11,12 @@ IPADDRESS = '192.168.1.189' ...@@ -11,15 +11,12 @@ IPADDRESS = '192.168.1.189'
11 11
12 BAUDRATE = 115200 12 BAUDRATE = 115200
13 13
14 folders = ['help', 'rooms', 'inventory', 'commands', 'mobs'] 14 folders = ['help', 'rooms', 'rooms/town', 'rooms/wilderness', 'inventory', 'commands', 'mobs']
15 15
16 files = [ 16 files = [
17 "main.py",
18 "mobs.txt",
19 "spawner.txt",
20 "welcome.txt", 17 "welcome.txt",
21 "wifiweb.py", 18 "defaultplayer.json",
22 "defaultplayer.json" 19 "main.py"
23 ] 20 ]
24 21
25 def run_command(sio, command, expected='>>>'): 22 def run_command(sio, command, expected='>>>'):
...@@ -27,18 +24,21 @@ def run_command(sio, command, expected='>>>'): ...@@ -27,18 +24,21 @@ def run_command(sio, command, expected='>>>'):
27 sio.flush() # it is buffering. required to get the data out *now* 24 sio.flush() # it is buffering. required to get the data out *now*
28 res = '' 25 res = ''
29 while expected not in res: 26 while expected not in res:
27 try:
30 res = sio.readline() 28 res = sio.readline()
31 # print(res) 29 except UnicodeDecodeError:
30 return ''
32 return res 31 return res
33 32
34 with serial.Serial(PORT, BAUDRATE, timeout=1) as ser: 33 with serial.Serial(PORT, BAUDRATE, timeout=1) as ser:
35 sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser)) 34 sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))
36 run_command(sio, '\x03') 35 run_command(sio, '\x03')
37 run_command(sio, 'import os') 36 run_command(sio, 'import os')
38 root = eval(run_command(sio, 'os.listdir()', expected=']')) 37 root = run_command(sio, 'os.listdir()', expected=']')
39
40 if not set(folders).issubset(root):
41 38
39 if 'EEXIST' in root:
40 print('Folders already created.')
41 else:
42 print('Creating folders.') 42 print('Creating folders.')
43 # we are missing folders so they need to be created. 43 # we are missing folders so they need to be created.
44 tmp_folders = folders 44 tmp_folders = folders
...@@ -48,18 +48,19 @@ with serial.Serial(PORT, BAUDRATE, timeout=1) as ser: ...@@ -48,18 +48,19 @@ with serial.Serial(PORT, BAUDRATE, timeout=1) as ser:
48 continue 48 continue
49 print('Creating folder: {}'.format(folder)) 49 print('Creating folder: {}'.format(folder))
50 run_command(sio, 'os.mkdir("{}")'.format(folder)) 50 run_command(sio, 'os.mkdir("{}")'.format(folder))
51 else:
52 print('Folders already created.')
53 51
54 52
55 for folder in folders: 53 for folder in folders:
56 for f in os.listdir(folder): 54 for f in os.listdir(folder):
55 if not os.path.isdir(f):
57 files.append('{}/{}'.format(folder, f)) 56 files.append('{}/{}'.format(folder, f))
58 57
59 with open('releasepw.conf', 'r', encoding='utf-8') as f: 58 with open('releasepw.conf', 'r', encoding='utf-8') as f:
60 password = f.read() 59 password = f.read()
61 60
62 for file in files: 61 for file in files:
63 os.system("python webrepl\webrepl_cli.py -p {} {} {}:/{}".format(password, file, IPADDRESS, file)) 62 # print("python webrepl\\webrepl_cli.py -p {} {} {}:/{}".format(password, file, IPADDRESS, file))
63 os.system("python webrepl\\webrepl_cli.py -p {} {} {}:/{}".format(password, file, IPADDRESS, file))
64 64
65 run_command(sio, 'import machine;machine.reset') 65 print("Rebooting via machine reset")
66 run_command(sio, 'import machine;machine.reset()')
......
1 import os
2 import io
3 import serial
4 import time
5
6 ######################################
7 # EDIT THIS TO MATCH YOUR SETTINGS
8 PORT = 'COM17'
9 VMID = '1e5fc8c'
10 ESSID = 'Volley'
11 WIFI_PASSWORD = '6198472223'
12 ######################################
13
14 BAUDRATE = 115200
15
16 baked_files = [
17 "weemud.py",
18 "mudserver.py",
19 "commandhandler.py",
20 "wifiweb.py",
21 "utils.py"
22 ]
23
24
25 def run_command(sio, command, expected='>>>'):
26 sio.write("{}\n".format(command))
27 sio.flush() # it is buffering. required to get the data out *now*
28 res = ''
29 while expected not in res:
30 try:
31 res = sio.readline()
32 except UnicodeDecodeError:
33 return ''
34 return res
35
36 # Compile vagrant image
37 for file in baked_files:
38 os.system("vagrant scp {} {}:micropython/ports/esp8266/modules/{}".format(file, VMID, file))
39 os.system('vagrant ssh {} -c "cd micropython/ports/esp8266;make"'.format(VMID))
40 try:
41 os.mkdir('image')
42 except FileExistsError:
43 pass
44 os.system('vagrant scp {}:micropython/ports/esp8266/build/firmware-combined.bin image/firmware-combined.bin'.format(VMID))
45
46 os.system('esptool --port {} erase_flash'.format(PORT))
47 os.system('esptool --port {} --baud 460800 write_flash --flash_size=detect 0 image/firmware-combined.bin'.format(PORT))
48
49 print("Sleeping 10 seconds for reboot")
50 time.sleep(10)
51
52 with open('releasepw.conf', 'r', encoding='utf-8') as f:
53 WEBREPL_PASS = f.read()
54
55 with serial.Serial(PORT, BAUDRATE, timeout=1) as ser:
56 sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))
57 run_command(sio, '\x03')
58 run_command(sio, 'import os')
59 print('Setting up Webrepl')
60 run_command(sio, "f = open('webrepl_cfg.py', 'w+').write(\"PASS = '{}'\\n\")".format(WEBREPL_PASS))
61 run_command(sio, 'f = open("boot.py", "w+").write("# This file is executed on every boot (including wake-boot from deepsleep)\\n#import esp\\n#esp.osdebug(None)\\nimport gc\\nimport webrepl\\nwebrepl.start()\\ngc.collect()\\n")')
62
63 print('Setting up Wifi Network')
64 run_command(sio, 'import network')
65 run_command(sio, 'sta_if = network.WLAN(network.STA_IF)')
66 run_command(sio, 'ap_if = network.WLAN(network.AP_IF)')
67
68 run_command(sio, 'sta_if.active(True)')
69
70 print('Connecting to {}'.format(ESSID))
71 run_command(sio, "sta_if.connect('{}', '{}')".format(ESSID, WIFI_PASSWORD))
72
73 waiting_for_ip = True
74 while waiting_for_ip:
75 try:
76 ifconfig = eval(run_command(sio, "sta_if.ifconfig()", expected='('))
77
78 if ifconfig[0] != '0.0.0.0':
79 print("New IP Address: {}".format(ifconfig[0]))
80 waiting_for_ip = False
81 except SyntaxError:
82 pass
83 except NameError:
84 pass
85
86 print("Rebooting via machine reset")
87
88 run_command(sio, 'import machine;machine.reset()')
89
90 print('Starting the squishy mud release')
91 time.sleep(5)
92 # Run the rest of the mud setup
93 import release
...\ No newline at end of file ...\ No newline at end of file
1 import json 1 import json
2 import sys 2 import sys
3 import re
4 3
5 if 'esp' in sys.platform: 4 if 'esp' in sys.platform:
6 from urandom import getrandbits 5 from urandom import getrandbits
...@@ -12,7 +11,7 @@ codes = {'resetall': 0, 'bold': 1, 'underline': 4, ...@@ -12,7 +11,7 @@ codes = {'resetall': 0, 'bold': 1, 'underline': 4,
12 'blinkoff': 25, 'underlineoff': 24, 'reverseoff': 27, 11 'blinkoff': 25, 'underlineoff': 24, 'reverseoff': 27,
13 'reset': 0, 'black': 30, 'red': 31, 'green': 32, 12 'reset': 0, 'black': 30, 'red': 31, 'green': 32,
14 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 13 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36,
15 'white': 37} 14 'white': 37 }
16 15
17 16
18 def password_hash(name, password): 17 def password_hash(name, password):
...@@ -37,13 +36,10 @@ def get_color(name): ...@@ -37,13 +36,10 @@ def get_color(name):
37 return '\x1b[{}m'.format(codes.get(name, 0)) 36 return '\x1b[{}m'.format(codes.get(name, 0))
38 37
39 38
40 def multiple_replace(text, adict): 39 def multiple_replace(text, replace_words):
41 rx = re.compile('|'.join(map(re.escape, adict))) 40 for word, replacement in replace_words.items():
42 41 text = text.replace(word, get_color(replacement))
43 def one_xlat(match): 42 return text
44 return get_color(adict[match.group(0)])
45 return rx.sub(one_xlat, text)
46
47 43
48 def save_object_to_file(obj, filename): 44 def save_object_to_file(obj, filename):
49 with open(filename.lower(), 'w', encoding='utf-8') as f: 45 with open(filename.lower(), 'w', encoding='utf-8') as f:
......
1 #!/usr/bin/env python
2
3 """
4 MudServer author: Mark Frimston - mfrimston@gmail.com
5
6 Micropython port and expansion author: Barry Ruffner - barryruffner@gmail.com
7 """
8
9 from time import sleep
10 from math import floor
11 from sys import platform
12 from os import listdir
13
14 from mudserver import MudServer
15 from commandhandler import CommandHandler
16 from utils import load_object_from_file, save_object_to_file, password_hash, calc_att, get_att
17
18 if 'esp' in platform:
19 from gc import collect, mem_free
20 from machine import Pin
21 # import the MUD server class
22
23
24 print('STARTING MUD\r\n\r\n\r\n')
25
26 # Setup button so when pressed the mud goes in to wifi hotspot mode on 192.168.4.1
27 if 'esp' in platform:
28 flash_button = Pin(0, Pin.IN, Pin.PULL_UP)
29
30 # stores the players in the game
31 players = {}
32
33 # start the server
34 globals()['mud'] = MudServer()
35
36
37 def show_prompt(pid):
38 if "prompt" not in players[pid]:
39 players[pid]["prompt"] = "> "
40 if 'hp' not in players[pid]:
41 players[pid]["hp"] = 100
42 if 'mp' not in players[pid]:
43 players[pid]["mp"] = 100
44 if 'sta' not in players[pid]:
45 players[pid]["sta"] = 10
46
47 prompt = players[pid]["prompt"].replace('%st', str(floor(players[pid]['sta']))).replace('%hp', str(floor(players[pid]["hp"]))).replace('%mp', str(floor(players[pid]["mp"])))
48 mud.send_message(pid, "\r\n" + prompt, '')
49
50 tick = 0.0
51 spawn = 0.0
52 cmd_handler = CommandHandler()
53
54 def spawn_mobs(players):
55 rooms = listdir('rooms')
56 for room in rooms:
57 if '_monsters.json' not in room:
58 continue
59 room_monsters = load_object_from_file('rooms/{}'.format(room))
60 for mon_name, monster in room_monsters.items():
61 monster_template = load_object_from_file('mobs/{}.json'.format(mon_name))
62 if not monster_template:
63 continue
64 while len(room_monsters[mon_name]['active']) < monster['max']:
65 print('Spawning {} in {}'.format(mon_name, room))
66 mp = get_att(monster_template['spawn']["mp"])
67 hp = get_att(monster_template['spawn']["hp"])
68 sta = get_att(monster_template['spawn']["sta"])
69 new_active = {
70 "mp": mp,
71 "maxhp": hp,
72 "hp": hp,
73 "sta": sta,
74 "maxmp": mp,
75 "target": "",
76 "action": "attack",
77 "maxsta": sta}
78 room_monsters[mon_name]['active'].append(new_active)
79 for pid, pl in players.items():
80 if players[pid]['room'].lower() == room.split('_')[0]:
81 mud.send_message(pid, "a {} arrived".format(mon_name))
82 save_object_to_file(room_monsters, 'rooms/{}'.format(room))
83
84 def run_mobs(players, mud):
85 for pid, player in players.items():
86 if not player or not player.get("name") or not player.get('password'):
87 continue
88 if player['mp'] < player['maxmp']:
89 players[pid]['mp'] += player['mpr']
90 if player['sta'] < player['maxsta']:
91 players[pid]['sta'] += player['star']
92 room_monsters = load_object_from_file('rooms/{}_monsters.json'.format(player['room']))
93 for mon_name, monster in room_monsters.items():
94 monster_template = load_object_from_file('mobs/{}.json'.format(mon_name))
95 for active_monster_idx, active_monster in enumerate(monster['active']):
96 sta = active_monster['sta']
97 if active_monster['mp'] < active_monster['maxmp']:
98 active_monster['mp'] += monster_template['mpr']
99 if sta < active_monster['maxsta']:
100 sta += monster_template['star']
101 active_monster['sta'] = sta
102 if active_monster['action'] == "attack" and active_monster['target'] == player['name']:
103 if player.get("weapon"):
104 weapon = load_object_from_file('inventory/{}.json'.format(player['weapon']))
105 att = get_att(weapon['damage'])
106 mud.send_message(pid, "Your %s strikes the %s for %d" % (weapon['title'], mon_name, att,), color='yellow')
107 else:
108 att = get_att(player['aa'])
109 mud.send_message(pid, "You hit the %s for %d" % (mon_name, att,), color='yellow')
110 active_monster['hp'] -= att
111 if active_monster['hp'] <= 0:
112 del room_monsters[mon_name]['active'][active_monster_idx]
113 mud.send_message(pid, "The %s dies." % (mon_name,), color=['bold', 'blue'])
114 break
115 if active_monster.get("weapon"):
116 weapon = load_object_from_file('inventory/{}.json'.format(active_monster['weapon']))
117 att = get_att(weapon['damage'])
118 mud.send_message(pid, "The %s strikes you with a %s for %d" % (mon_name, weapon['title'], att,), color='magenta')
119 else:
120 att = get_att(monster_template['aa'])
121 mud.send_message(pid, "You were hit by a %s for %d" % (mon_name, att,), color='magenta')
122 players[pid]['hp'] -= att
123 if (active_monster['hp']/active_monster['maxhp'] < 0.25) or (active_monster['mp'] > 0 and active_monster['mp']/active_monster['maxmp'] > 0.5) or (sta > 0 and sta/active_monster['maxsta'] > 0.5):
124 magic_cast = False
125 if active_monster['mp'] > 0:
126 att, active_monster['mp'] = calc_att(mud, pid, monster_template['sp'], active_monster['mp'])
127 players[pid]['hp'] -= att
128 if att > 0:
129 magic_cast = True
130 if not magic_cast:
131 if sta > 0:
132 att, sta = calc_att(mud, pid, monster_template['at'], sta)
133 active_monster['sta'] = sta
134 players[pid]['hp'] -= att
135
136 room_monsters[mon_name]['active'][active_monster_idx] = active_monster
137
138 save_object_to_file(room_monsters, 'rooms/{}_monsters.json'.format(player['room']))
139
140 def isalnum(c):
141 for letter in c:
142 if not letter.isalpha() and not letter.isdigit():
143 return False
144 return True
145
146 # main game loop. We loop forever (i.e. until the program is terminated)
147 while True:
148 if 'esp' in platform:
149 collect()
150 if flash_button.value() == 0:
151 break
152 # pause for 1/5 of a second on each loop, so that we don't constantly
153 sleep(0.002)
154 if 'esp' in platform:
155 tick += 0.001
156 else:
157 tick += 0.0005
158 if spawn >= 10:
159 spawn = 0
160 try:
161 spawn_mobs(players)
162 except Exception as e:
163 print('spawner error:')
164 print(e)
165 if 'esp' in platform:
166 collect()
167 if tick >= 1:
168 if 'esp' in platform:
169 print(mem_free())
170 spawn += tick
171 tick = 0
172 try:
173 run_mobs(players, mud)
174 except Exception as e:
175 print('mob error:')
176 print(e)
177 # 'update' must be called in the loop to keep the game running and give
178 # us up-to-date information
179 mud.update()
180
181 # go through any newly connected players
182 for id in mud.get_new_players():
183
184 # add the new player to the dictionary, noting that they've not been
185 # named yet.
186 # The dictionary key is the player's id number. We set their room to
187 # None initially until they have entered a name
188 # Try adding more player stats - level, gold, inventory, etc
189 players[id] = load_object_from_file("defaultplayer.json")
190 with open('welcome.txt', 'r', encoding='utf-8') as f:
191 for line in f:
192 mud.send_message(id, line, "\r")
193 # send the new player a prompt for their name
194 #bytes_to_send = bytearray([mud._TN_INTERPRET_AS_COMMAND, mud._TN_WONT, mud._ECHO])
195 #mud.raw_send(id, bytes_to_send)
196 mud.send_message(id, "What is your name?")
197
198 # go through any recently disconnected players
199 for id in mud.get_disconnected_players():
200
201 # if for any reason the player isn't in the player map, skip them and
202 # move on to the next one
203 if id not in players:
204 continue
205
206 # go through all the players in the game
207 for pid, pl in players.items():
208 # send each player a message to tell them about the diconnected
209 # player
210 if players[pid].get("name") is not None:
211 mud.send_message(pid, "{} quit the game".format(players[pid]["name"]))
212
213 # remove the player's entry in the player dictionary
214 if players[id]["name"] is not None:
215 save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
216 del(players[id])
217
218 # go through any new commands sent from players
219 for id, command, params in mud.get_commands():
220
221 # if for any reason the player isn't in the player map, skip them and
222 # move on to the next one
223 if id not in players:
224 continue
225
226 # if the player hasn't given their name yet, use this first command as
227 # their name and move them to the starting room.
228 if players[id].get("name") is None:
229 if command.strip() == '' or len(command) > 12 or not isalnum(command) or command is None:
230 mud.send_message(id, "Invalid Name")
231 print("Bad guy!")
232 print(mud.get_remote_ip(id))
233 continue
234 already_logged_in = False
235 for pid, pl in players.items():
236 if players[pid].get("name") == command:
237 mud.send_message(id, "{} is already logged in.".format(command))
238 mud.disconnect_player(id)
239 continue
240 loaded_player = load_object_from_file("players/{}.json".format(command))
241 mud.remote_echo(id, False)
242 players[id]["name"] = command
243 if not loaded_player:
244 # Player does not exist.
245 mud.send_message(id, "Character does not exist. Please enter a password to create a new character: ")
246 else:
247 mud.send_message(id, "Enter Password: ")
248
249 elif players[id]["password"] is None:
250 if players[id].get("retries", 0) > 1:
251 mud.send_message(id, "Too many attempts")
252 mud.disconnect_player(id)
253 continue
254
255 if command.strip() == '' or command is None:
256 players[id]["retries"] = players[id].get("retries", 0) + 1
257 mud.send_message(id, "Invalid Password")
258 continue
259
260 loaded_player = load_object_from_file("players/{}.json".format(players[pid]["name"]))
261 if loaded_player is None:
262 players[id]["password"] = password_hash(players[pid]["name"], command)
263 players[id]["room"] = "town/tavern"
264 else:
265 if loaded_player["password"] == password_hash(players[pid]["name"], command):
266 players[id] = loaded_player
267 else:
268 players[id]["retries"] = players[id].get("retries", 0) + 1
269 mud.send_message(id, "Invalid Password")
270 continue
271
272 # go through all the players in the game
273 for pid, pl in players.items():
274 # send each player a message to tell them about the new player
275 mud.send_message(pid, "{} entered the game".format(players[id]["name"]))
276
277 mud.remote_echo(id, True)
278 # send the new player a welcome message
279 mud.send_message(id, "\r\n\r\nWelcome to the game, {}. ".format(players[id]["name"]) +
280 "\r\nType 'help' for a list of commands. Have fun!\r\n\r\n")
281
282 # send the new player the description of their current room
283
284 cmd_handler.parse(id, 'look', '', mud, players)
285 show_prompt(id)
286 else:
287 if 'esp' in platform:
288 collect()
289
290 cmd_handler.parse(id, command, params, mud, players)
291 show_prompt(id)
292
293 # Start WIFI Setup
294 if 'esp' in platform:
295 if flash_button.value() == 0:
296 print('Starting WIFIWeb')
297 import wifiweb