weemud.py 12.4 KB
#!/usr/bin/env python

"""
MudServer author: Mark Frimston - mfrimston@gmail.com

Micropython port and expansion author: Barry Ruffner - barryruffner@gmail.com
"""

from time import sleep
from math import floor
from sys import platform
from os import listdir

from mudserver import MudServer
from commandhandler import CommandHandler
from utils import load_object_from_file, save_object_to_file, password_hash, calc_att, get_att

if 'esp' in platform:
    from gc import collect, mem_free
    from machine import Pin
# import the MUD server class


print('STARTING MUD\r\n\r\n\r\n')

# Setup button so when pressed the mud goes in to wifi hotspot mode on 192.168.4.1
if 'esp' in platform:
    flash_button = Pin(0, Pin.IN, Pin.PULL_UP)

# stores the players in the game
players = {}

# start the server
globals()['mud'] = MudServer()


def show_prompt(pid):
    if "prompt" not in players[pid]:
        players[pid]["prompt"] = "> "
    if 'hp' not in players[pid]:
        players[pid]["hp"] = 100
    if 'mp' not in players[pid]:
        players[pid]["mp"] = 100
    if 'sta' not in players[pid]:
        players[pid]["sta"] = 10

    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"])))
    mud.send_message(pid, "\r\n" + prompt, '')

tick = 0.0
spawn = 0.0
cmd_handler = CommandHandler()

def spawn_mobs(players):
    rooms = listdir('rooms')
    for room in rooms:
        if '_monsters.json' not in room:
            continue
        room_monsters = load_object_from_file('rooms/{}'.format(room))
        for mon_name, monster in room_monsters.items():
            monster_template = load_object_from_file('mobs/{}.json'.format(mon_name))
            if not monster_template:
                continue
            while len(room_monsters[mon_name]['active']) < monster['max']:
                print('Spawning {} in {}'.format(mon_name, room))
                mp = get_att(monster_template['spawn']["mp"])
                hp = get_att(monster_template['spawn']["hp"])
                sta = get_att(monster_template['spawn']["sta"])
                new_active = {
                    "mp": mp, 
                    "maxhp": hp,
                    "hp": hp,
                    "sta": sta,
                    "maxmp": mp,
                    "target": "",
                    "action": "attack",
                    "maxsta": sta}
                room_monsters[mon_name]['active'].append(new_active)
                for pid, pl in players.items():
                    if players[pid]['room'].lower() == room.split('_')[0]:
                        mud.send_message(pid, "a {} arrived".format(mon_name))
        save_object_to_file(room_monsters, 'rooms/{}'.format(room))

def run_mobs(players, mud):
    for pid, player in players.items():
        if not player or not player.get("name") or not player.get('password'):
            continue
        if player['mp'] < player['maxmp']:
            players[pid]['mp'] += player['mpr']
        if player['sta'] < player['maxsta']:
            players[pid]['sta'] += player['star']
        room_monsters = load_object_from_file('rooms/{}_monsters.json'.format(player['room']))
        for mon_name, monster in room_monsters.items():
            monster_template = load_object_from_file('mobs/{}.json'.format(mon_name))
            for active_monster_idx, active_monster in enumerate(monster['active']):
                sta = active_monster['sta']
                if active_monster['mp'] < active_monster['maxmp']:
                    active_monster['mp'] += monster_template['mpr']
                if sta < active_monster['maxsta']:
                    sta += monster_template['star']
                    active_monster['sta'] = sta
                if active_monster['action'] == "attack" and active_monster['target'] == player['name']:
                    if player.get("weapon"):
                        weapon = load_object_from_file('inventory/{}.json'.format(player['weapon']))
                        att = get_att(weapon['damage'])
                        mud.send_message(pid, "Your %s strikes the %s for %d" % (weapon['title'], mon_name, att,), color='yellow')
                    else:
                        att = get_att(player['aa'])
                        mud.send_message(pid, "You hit the %s for %d" % (mon_name, att,), color='yellow')
                    active_monster['hp'] -= att
                    if active_monster['hp'] <= 0:
                        del room_monsters[mon_name]['active'][active_monster_idx]
                        mud.send_message(pid, "The %s dies." % (mon_name,), color=['bold', 'blue'])
                        break
                    if active_monster.get("weapon"):
                        weapon = load_object_from_file('inventory/{}.json'.format(active_monster['weapon']))
                        att = get_att(weapon['damage'])
                        mud.send_message(pid, "The %s strikes you with a %s for %d" % (mon_name, weapon['title'], att,), color='magenta')
                    else:
                        att = get_att(monster_template['aa'])
                        mud.send_message(pid, "You were hit by a %s for %d" % (mon_name, att,), color='magenta')
                    players[pid]['hp'] -= att
                    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):
                        magic_cast = False
                        if active_monster['mp'] > 0:
                            att, active_monster['mp'] = calc_att(mud, pid, monster_template['sp'], active_monster['mp'])
                            players[pid]['hp'] -= att
                            if att > 0:
                                magic_cast = True                            
                        if not magic_cast:
                            if sta > 0:
                                att, sta = calc_att(mud, pid, monster_template['at'], sta)
                                active_monster['sta'] = sta
                                players[pid]['hp'] -= att

                    room_monsters[mon_name]['active'][active_monster_idx] = active_monster
            
        save_object_to_file(room_monsters, 'rooms/{}_monsters.json'.format(player['room']))

def isalnum(c):
    for letter in c:
        if not letter.isalpha() and not letter.isdigit():
            return False
    return True

# main game loop. We loop forever (i.e. until the program is terminated)
while True:
    if 'esp' in platform:
        collect()
        if flash_button.value() == 0:
            break
    # pause for 1/5 of a second on each loop, so that we don't constantly
    sleep(0.002)
    if 'esp' in platform:
        tick += 0.001
    else:
        tick += 0.0005
    if spawn >= 10:
        spawn = 0
        try:
            spawn_mobs(players)
        except Exception as e:
            print('spawner error:')
            print(e)
        if 'esp' in platform:
            collect()
    if tick >= 1:
        if 'esp' in platform:
            print(mem_free())
        spawn += tick
        tick = 0
        try:
            run_mobs(players, mud)
        except Exception as e:
            print('mob error:')
            print(e)
    # 'update' must be called in the loop to keep the game running and give
    # us up-to-date information
    mud.update()

    # go through any newly connected players
    for id in mud.get_new_players():

        # add the new player to the dictionary, noting that they've not been
        # named yet.
        # The dictionary key is the player's id number. We set their room to
        # None initially until they have entered a name
        # Try adding more player stats - level, gold, inventory, etc
        players[id] = load_object_from_file("defaultplayer.json")
        with open('welcome.txt', 'r', encoding='utf-8') as f:
            for line in f:
                mud.send_message(id, line, "\r")
        # send the new player a prompt for their name
        #bytes_to_send = bytearray([mud._TN_INTERPRET_AS_COMMAND, mud._TN_WONT, mud._ECHO])
        #mud.raw_send(id, bytes_to_send)
        mud.send_message(id, "What is your name?")

    # go through any recently disconnected players
    for id in mud.get_disconnected_players():

        # if for any reason the player isn't in the player map, skip them and
        # move on to the next one
        if id not in players:
            continue

        # go through all the players in the game
        for pid, pl in players.items():
            # send each player a message to tell them about the diconnected
            # player
            if players[pid].get("name") is not None:
                mud.send_message(pid, "{} quit the game".format(players[pid]["name"]))

        # remove the player's entry in the player dictionary
        if players[id]["name"] is not None:
            save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
        del(players[id])

    # go through any new commands sent from players
    for id, command, params in mud.get_commands():

        # if for any reason the player isn't in the player map, skip them and
        # move on to the next one
        if id not in players:
            continue

        # if the player hasn't given their name yet, use this first command as
        # their name and move them to the starting room.
        if players[id].get("name") is None:
            if command.strip() == '' or len(command) > 12 or not isalnum(command) or command is None:
                mud.send_message(id, "Invalid Name")
                print("Bad guy!")
                print(mud.get_remote_ip(id))
                continue
            already_logged_in = False
            for pid, pl in players.items():
                if players[pid].get("name") == command:
                    mud.send_message(id, "{} is already logged in.".format(command))
                    mud.disconnect_player(id)
                    continue
            loaded_player = load_object_from_file("players/{}.json".format(command))
            mud.remote_echo(id, False)
            players[id]["name"] = command
            if not loaded_player:
                # Player does not exist.
                mud.send_message(id, "Character does not exist. Please enter a password to create a new character: ")
            else:
                mud.send_message(id, "Enter Password: ")

        elif players[id]["password"] is None:
            if players[id].get("retries", 0) > 1:
                mud.send_message(id, "Too many attempts")
                mud.disconnect_player(id)
                continue

            if command.strip() == '' or command is None:
                players[id]["retries"] = players[id].get("retries", 0) + 1
                mud.send_message(id, "Invalid Password")
                continue

            loaded_player = load_object_from_file("players/{}.json".format(players[pid]["name"]))
            if loaded_player is None:
                players[id]["password"] = password_hash(players[pid]["name"], command)
                players[id]["room"] = "town/tavern"
            else:
                if loaded_player["password"] == password_hash(players[pid]["name"], command):
                    players[id] = loaded_player
                else:
                    players[id]["retries"] = players[id].get("retries", 0) + 1
                    mud.send_message(id, "Invalid Password")
                    continue

            # go through all the players in the game
            for pid, pl in players.items():
                # send each player a message to tell them about the new player
                mud.send_message(pid, "{} entered the game".format(players[id]["name"]))

            mud.remote_echo(id, True)
            # send the new player a welcome message
            mud.send_message(id, "\r\n\r\nWelcome to the game, {}. ".format(players[id]["name"]) +
                             "\r\nType 'help' for a list of commands. Have fun!\r\n\r\n")

            # send the new player the description of their current room

            cmd_handler.parse(id, 'look', '', mud, players)
            show_prompt(id)
        else:
            if 'esp' in platform:
                collect()

            cmd_handler.parse(id, command, params, mud, players)
            show_prompt(id)

# Start WIFI Setup
if 'esp' in platform:
    if flash_button.value() == 0:
        print('Starting WIFIWeb')
        import wifiweb