d809b92a by Barry

Added full combat, spawner, emotes, actions, casting spells, perform,

spells, help for spells and other commands
1 parent 2f3e968d
......@@ -11,6 +11,9 @@ global_aliases = {
'\'': 'say',
'i': 'inventory',
'inv': 'inventory',
'attack': 'kill',
',': 'emote',
'social': 'emote'
}
class CommandHandler(object):
......@@ -36,6 +39,12 @@ class CommandHandler(object):
if cmd in utils.load_object_from_file('rooms/' + players[id]["room"] + '.json').get('exits'):
params = cmd + " " + params.lower().strip()
cmd = "go"
if cmd in players[id]["sp"]:
params = cmd + " " + params.lower().strip()
cmd = "cast"
if cmd in players[id]["at"]:
params = cmd + " " + params.lower().strip()
cmd = "perform"
if cmd == '':
return True
command = cmd
......
def actions(id, players, mud):
mud.send_message(id, '\r\n-Action----------=Actions=-----------Cost-\r\n')
for name, action in players[id]['at'].items():
mud.send_message(id, '{} {}'.format("%-37s" % name, action['cost']))
mud.send_message(id, '\r\n------------------------------------------\r\n')
mud.send_message(id, 'For more detail on a specific action use "help <action>"')
actions(id, players, mud)
del actions
\ No newline at end of file
import utils
# for now spells can only be cast on the thing you are fighting since I don't have a good lexer to pull
# out targets. This also means that spells are offensive only since you cant cast on 'me'
# TODO: Add proper parsing so you can do: cast fireball on turkey or even better, cast fireball on the first turkey
def cast(id, params, players, mud, command):
if params == '':
params = command.strip()
else:
params = params.strip()
if len(params) == 0:
mud.send_message(id, 'What spell do you want to cast?')
return True
spell = players[id]['sp'].get(params)
if not spell:
mud.send_message(id, 'You do not appear to know how to cast %s.' % (params,))
return True
mp = players[id]['mp']
if mp > 0 and mp > spell['cost']:
room_monsters = utils.load_object_from_file('rooms/{}_monsters.json'.format(players[id]['room']))
if not room_monsters:
mud.send_message(id, 'There is nothing here to cast that spell on.')
for mon_name, monster in room_monsters.items():
monster_template = utils.load_object_from_file('mobs/{}.json'.format(mon_name))
if not monster_template:
continue
fighting = False
for active_monster_idx, active_monster in enumerate(monster['active']):
if active_monster['action'] == "attack" and active_monster['target'] == players[id]['name']:
fighting = True
att, mp = utils.calc_att(mud, id, [], mp, spell)
players[id]['mp'] = mp
active_monster['hp'] -= att
# don't let them off without saving cheating sons of bees
utils.save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
room_monsters[mon_name]['active'][active_monster_idx] = active_monster
if not fighting:
mud.send_message(id, 'You are not currently in combat')
return True
utils.save_object_to_file(room_monsters, 'rooms/{}_monsters.json'.format(players[id]['room']))
else:
mud.send_message(id, 'You do not have enough mana to cast that spell.')
if command is None and cmd is not None:
command = cmd
cast(id, params.strip(), players, mud, command)
del cast
\ No newline at end of file
def emote(id, params, players, mud):
# go through every player in the game
for pid, pl in players.items():
# if they're in the same room as the player
if players[pid]["room"] == players[id]["room"]:
# send them a message telling them what the player said
mud.send_message(pid, "{} {}".format(players[id]["name"], params))
emote(id, params, players, mud)
del emote
\ No newline at end of file
def kill(id, params, players, mud):
if len(params) == 0:
mud.send_message(id, 'What did you want to attack?')
return True
room_monsters = utils.load_object_from_file('rooms/{}_monsters.json'.format(players[id]['room']))
for mon_name, monster in room_monsters.items():
if mon_name in params.lower():
if len(monster['active']) > 0:
if monster['active'][0]['target'] == players[id]['name']:
mud.send_message(id, 'You are already engaged in combat with that target.')
return True
else:
room_monsters[mon_name]['active'][0]['target'] = players[id]['name']
mud.send_message(id, 'You attack the {}.'.format(mon_name), color=['red', 'bold'])
utils.save_object_to_file(room_monsters, 'rooms/{}_monsters.json'.format(players[id]['room']))
return True
if params[0] in 'aeiou':
mud.send_message(id, 'You do not see an {} here.'.format(params))
else:
mud.send_message(id, 'You do not see a {} here.'.format(params))
kill(id, params, players, mud)
del kill
\ No newline at end of file
def look(id, mud, players, tokens):
room_name = players[id]["room"]
room_data = utils.load_object_from_file('rooms/' + room_name + '.json')
room_monsters = utils.load_object_from_file('rooms/' + room_name + '_monsters.json')
mud.send_message(id, "")
if len(tokens) > 0 and tokens[0] == 'at':
del tokens[0]
......@@ -21,7 +22,14 @@ def look(id, mud, players, tokens):
item = utils.load_object_from_file('inventory/' + subject + '.json')
mud.send_message(id, 'You check your inventory and see {}'.format(item.get('description')))
return True
if subject in room_monsters:
if len(room_monsters[subject]['active']) > 0:
monster_template = utils.load_object_from_file('mobs/{}.json'.format(subject))
if monster_template:
mud.send_message(id, 'You see {}'.format(monster_template.get('desc').lower()))
return True
mud.send_message(id, "That doesn't seem to be here.")
return
playershere = []
# go through every player in the game
......@@ -29,22 +37,28 @@ def look(id, mud, players, tokens):
# if they're in the same room as the player
if players[pid]["room"] == players[id]["room"]:
# ... and they have a name to be shown
if players[pid]["name"] is not None:
if players[pid]["name"] is not None and players[id]["name"] != players[pid]["name"]:
# add their name to the list
playershere.append(players[pid]["name"])
# send player a message containing the list of players in the room
mud.send_message(id, "Players here: {}".format(", ".join(playershere)))
if len(playershere) > 0:
mud.send_message(id, "Players here: {}".format(", ".join(playershere)))
# send player a message containing the list of exits from this room
mud.send_message(id, "Exits are: {}".format(", ".join(room_data.get('exits'))))
# send player a message containing the list of exits from this room
mud.send_message(id, "Items here: {}".format(", ".join(room_data.get('inventory').keys())))
if len(room_data.get('inventory').keys()) > 0:
mud.send_message(id, "Items here: {}".format(", ".join(room_data.get('inventory').keys())))
# send player a message containing the list of players in the room
room_monsters = utils.load_object_from_file('rooms/' + room_name + '_monsters.json')
mud.send_message(id, "Mobs here: {}".format( ", ".join(room_monsters.keys())))
for mon_name, monster in room_monsters.items():
count = len(monster['active'])
if count > 1:
mud.send_message(id, "{} {} are here.".format(count, mon_name))
elif count > 0:
mud.send_message(id, "a {} is here.".format(mon_name))
look(id, mud, players, tokens)
del look
\ No newline at end of file
......
import utils
# for now actions can only be cast on the thing you are fighting since I don't have a good lexer to pull
# out targets. This also means that actions are offensive only since you cant cast on 'me'
# TODO: Add proper parsing so you can do: cast fireball on turkey or even better, cast fireball on the first turkey
def perform(id, params, players, mud, command):
if params == '':
params = command.strip()
else:
params = params.strip()
if len(params) == 0:
mud.send_message(id, 'What action do you want to perform?')
return True
action = players[id]['at'].get(params)
if not action:
mud.send_message(id, 'You do not appear to know the action %s.' % (params,))
return True
sta = players[id]['sta']
if sta > 0 and sta > action['cost']:
room_monsters = utils.load_object_from_file('rooms/{}_monsters.json'.format(players[id]['room']))
if not room_monsters:
mud.send_message(id, 'There is nothing here to perform that action on.')
for mon_name, monster in room_monsters.items():
monster_template = utils.load_object_from_file('mobs/{}.json'.format(mon_name))
if not monster_template:
continue
fighting = False
for active_monster_idx, active_monster in enumerate(monster['active']):
if active_monster['action'] == "attack" and active_monster['target'] == players[id]['name']:
fighting = True
att, sta = utils.calc_att(mud, id, [], sta, action)
players[id]['sta'] = sta
active_monster['hp'] -= att
# don't let them off without saving cheating sons of bees
utils.save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
room_monsters[mon_name]['active'][active_monster_idx] = active_monster
if not fighting:
mud.send_message(id, 'You are not currently in combat')
return True
utils.save_object_to_file(room_monsters, 'rooms/{}_monsters.json'.format(players[id]['room']))
else:
mud.send_message(id, 'You do not have enough stamina to perform that action.')
if command is None and cmd is not None:
command = cmd
perform(id, params.strip(), players, mud, command)
del perform
\ No newline at end of file
def spells(id, players, mud):
mud.send_message(id, '\r\n-Spell-----------=Spells=-----------Cost-\r\n')
for name, spell in players[id]['sp'].items():
mud.send_message(id, '{} {}'.format("%-37s" % name, spell['cost']))
mud.send_message(id, '\r\n-----------------------------------------\r\n')
mud.send_message(id, 'For more detail on a specific spell use "help <spell>"')
spells(id, players, mud)
del spells
\ No newline at end of file
......@@ -5,9 +5,9 @@
"prompt": "hp %hp mp %mp> ",
"aliases": {},
"hp": 100,
"mp": 100,
"mp": 10,
"maxhp": 100,
"maxmp": 100,
"maxmp": 10,
"maxsta": 10,
"sta": 10,
"aa": "1d2",
......@@ -15,5 +15,7 @@
"star": 0.4,
"weapon": null,
"sp": {},
"at": {}
"at": {
"kick": {"cost":5, "dmg": "2d4", "desc": "You unleash a powerful kick"}
}
}
\ No newline at end of file
......
Command:
actions
Usage:
actions
Description:
List all actions and the perform cost in stamina points (sta).
See Also:
perform
cast
spells
examine
\ No newline at end of file
......@@ -13,3 +13,8 @@ Default Aliases:
d: down
l: look
': say
,: emote
i: inventory
inv: inventory
attack: kill
social: emote
......
Command:
cast
Usage:
cast <spell>
Short Form:
<spell>
Description:
The cast command invokes a magic spell that you have learned and is in your list of spells. Magic requires the use of your mana points (mp) which is slowly regenerated. Spells may only be cast if you have the required mana even if you know the spell.
You may also call spells by their name directly,
e.g. > fireball
See Also:
spells
perform
examine
\ No newline at end of file
Command:
drop
Usage:
drop <item>
Description:
Drops an item in your inventory in the current room.
See Also:
inventory
get
look
Command:
emote
Usage:
emote <description>
, <description>
social <description>
Description:
The command causes you to display an action. You can use this to perform a role played action within a room.
Example:
emote smiles broadly
This will appear as:
Joe smiles broadly
Spell:
fireball
Usage:
cast fireball
Short Form:
fireball
Description:
A fireball explodes out from your fingertips on demand and blisters through the air at your target bursting into a ball of flame on contact.
See Also:
spells
cast
Command:
get
Usage:
get <item>
Description:
Picks up an item in the current room and adds it to your inventory.
See Also:
inventory
drop
look
......@@ -4,6 +4,9 @@ Command:
Usage:
go <exit>
Short Form:
<exit>
Description:
The go command moves through the exit specified. Common exits are
north, south, east, west, up, and down. Normally the exits will
......
......@@ -2,11 +2,19 @@ Additional information can be learned about a command by passing a command: help
Help Topics:
actions
aliases
cast
drop
get
go
inventory
look
perform
prompt
quit
save
whisper
say
spells
whisper
who
......
Command:
inventory
Aliases:
i
inv
Usage:
inventory
Description:
List all inventory and the quantity of each item.
See Also:
get
drop
Action:
kick
Usage:
perform kick
Short Form:
kick
Description:
Your foot snaps out at the current target causing blunt damage.
See Also:
actions
perform
Command:
perform
Usage:
perform <action>
Short Form:
<action>
Description:
The perform an action that you have learned and is in your list of actions. Special actions require the use of your stamina points (sta) which is slowly regenerated. Special actions may only be performed if you have the required stamina even if you know the action.
You may also perform actions by their name directly,
e.g. > kick
See Also:
actions
spells
cast
examine
\ No newline at end of file
Command:
spells
Usage:
spells
Description:
List all spells and the casting cost in mana points (mp).
See Also:
cast
perform
actions
examine
\ No newline at end of file
Spell:
zap
Usage:
cast zap
Short Form:
zap
Description:
A spark leaps out and strikes the target causing burning and electric damage.
See Also:
spells
cast
......@@ -15,6 +15,7 @@ if 'esp' in platform:
from mudserver import MudServer
from utils import load_object_from_file, save_object_to_file
from math import floor
print('STARTING MUD\r\n\r\n\r\n')
......@@ -37,7 +38,7 @@ def show_prompt(pid):
if 'sta' not in players[pid]:
players[pid]["sta"] = 10
prompt = players[pid]["prompt"].replace('%hp', str(players[pid]["hp"])).replace('%mp', str(players[pid]["mp"]))
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
......@@ -54,7 +55,7 @@ while True:
tick += 0.001
else:
tick += 0.0005
if spawn >= 30:
if spawn >= 10:
spawn = 0
try:
ldict = {}
......
......@@ -5,6 +5,10 @@ def run_mobs(players, mud):
for pid, player in players.items():
if not player['name']:
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']))
if not room_monsters:
continue
......@@ -26,20 +30,23 @@ def run_mobs(players, mud):
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,))
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,))
mud.send_message(pid, "You hit the %s for %d" % (mon_name, att,), color='yellow')
hp -= att
if hp == 0:
DEAD = 1
if hp <= 0:
del room_monsters[mon_name]['active'][active_monster_idx]
mud.send_message(pid, "The %s dies." % (mon_name,), color=['bold', 'blue'])
# TODO: Create a corpse
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,))
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,))
mud.send_message(pid, "You were hit by a %s for %d" % (mon_name, att,), color='magenta')
players[pid]['hp'] -= att
# wait until at least 50% of stamina or mp are regenerated or hp is less than 25%
......
......@@ -12,7 +12,7 @@ import socket
import select
import time
import sys
from utils import get_color
class MudServer(object):
"""A basic server for text-based Multi-User Dungeon (MUD) games.
......@@ -181,7 +181,7 @@ class MudServer(object):
# return the info list
return retval
def send_message(self, to, message, line_ending='\r\n'):
def send_message(self, to, message, line_ending='\r\n', color=None):
"""Sends the text in the 'message' parameter to the player with
the id number given in the 'to' parameter. The text will be
printed out in the player's terminal.
......@@ -190,7 +190,14 @@ class MudServer(object):
# message on its own line
chunks, chunk_size = len(message), 80 #len(x)/4
lines = [ message[i:i+chunk_size] for i in range(0, chunks, chunk_size) ]
self._attempt_send(to, '\r\n'.join(lines) + line_ending)
if color:
if isinstance(color, list):
colors = ''.join([get_color(c) for c in color])
self._attempt_send(to, colors + '\r\n'.join(lines) + line_ending + get_color('reset'))
else:
self._attempt_send(to, get_color(color) + '\r\n'.join(lines) + line_ending + get_color('reset'))
else:
self._attempt_send(to, '\r\n'.join(lines) + line_ending)
def shutdown(self):
"""Closes down the server, disconnecting all clients and
......
{"name": "test", "room": "Tavern", "inventory": {"candle": 1}, "prompt": "hp %hp mp %mp> ", "aliases": {}, "hp": 461, "mp": 50, "sta": 10, "aa": "1d2", "mpr": 0.25, "star": 0.4, "maxhp": 953, "maxmp": 100, "maxsta": 10, "weapon": "sword", "sp": {"fireball": {"cost": 5, "dmg": "2d4", "desc": "A fireball blasts from your fingertips striking the target"}}, "at": {}}
\ No newline at end of file
{"name": "test", "room": "Room001", "inventory": {"candle": 1}, "prompt": "h %hp m %mp s %st> ", "aliases": {}, "hp": 100, "mp": 10.0, "sta": 10.200000000000031, "aa": "1d2", "mpr": 0.25, "star": 0.4, "maxhp": 953, "maxmp": 10, "maxsta": 10, "weapon": "sword", "sp": {"fireball": {"cost": 5, "dmg": "2d4", "desc": "A fireball blasts from your fingertips striking the target"}}, "at": {"kick": {"cost": 5, "dmg": "2d4", "desc": "You unleash a powerful kick"}}}
\ No newline at end of file
......
{
"title": "Behind the bar",
"description": "The back of the bar gives a full view of the tavern. The bar top is a large wooden plank thrown across some barrels.",
"exits": {"tavern": "Tavern"},
"look_items": {
"bar": "The bar is a long wooden plank thrown over roughly hewn barrels.",
"barrel,barrels": "The old barrels bands are thick with oxidation and stained with the purple of spilled wine.",
"wooden,oak,plank": "An old solid oak plank that has a large number of chips and drink marks."
},
"inventory": {}
}
{"title": "Behind the bar", "description": "The back of the bar gives a full view of the tavern. The bar top is a large wooden plank thrown across some barrels.", "exits": {"tavern": "Tavern"}, "look_items": {"bar": "The bar is a long wooden plank thrown over roughly hewn barrels.", "barrel,barrels": "The old barrels bands are thick with oxidation and stained with the purple of spilled wine.", "wooden,oak,plank": "An old solid oak plank that has a large number of chips and drink marks."}, "inventory": {}}
\ No newline at end of file
......
{}
\ No newline at end of file
{"cricket": {"max": 1, "active": [{"mp": 10, "maxhp": 3, "hp": 3, "sta": 10, "maxmp": 10, "target": "", "action": "attack", "maxsta": 10}]}}
\ No newline at end of file
......
{"cricket": {"max": 1, "active": [{"hp": 48, "mp": 5.0, "sta": 0.35000000000008313, "maxhp": 100, "maxmp": 10, "maxsta": 10, "action": "attack", "target": "test"}]}}
\ No newline at end of file
{"cricket": {"max": 2, "active": [{"mp": 10, "maxhp": 3, "hp": 3, "sta": 10, "maxmp": 10, "target": "", "action": "attack", "maxsta": 10}, {"mp": 10, "maxhp": 3, "hp": 3, "sta": 10, "maxmp": 10, "target": "", "action": "attack", "maxsta": 10}]}}
\ No newline at end of file
......
def spawn_mobs(players):
for pid, player in players.items():
print('checking spawn in room: ' + player['room'])
from os import listdir
from utils import get_att
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))
spawn_mobs(players)
\ No newline at end of file
......
......@@ -5,6 +5,19 @@ if 'esp' in sys.platform:
else:
import random
def get_color(name):
CODES = {'resetall': 0, 'bold': 1, 'underline': 4,
'blink': 5, 'reverse': 7, 'boldoff': 22,
'blinkoff': 25, 'underlineoff': 24, 'reverseoff': 27,
'reset': 0, 'black': 30, 'red': 31, 'green': 32,
'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36,
'white':37}
if name not in CODES:
return ''
return '\x1b[{}m'.format(CODES.get(name, 0))
def save_object_to_file(obj, filename):
with open(filename, 'w', encoding='utf-8') as f:
f.write(json.dumps(obj))
......@@ -61,6 +74,10 @@ def calc_att(mud, pid, attacks, bank, attack=None):
if len(v_att) > 0:
attack = v_att[randrange(len(v_att))]
att = get_att(attack['dmg'])
mud.send_message(pid, "%s for %d" % (attack['desc'], att,))
if attack:
mud.send_message(pid, "%s for %d" % (attack['desc'], att,), color=['bold', 'yellow'])
else:
mud.send_message(pid, "%s for %d" % (attack['desc'], att,), color=['bold', 'magenta'])
bank -= attack['cost']
return att, bank
\ No newline at end of file
......