From 75cc8bf1ad6d5408a596ade7dbd75be3ed3ed3a7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 31 Mar 2012 21:10:34 +0200 Subject: [PATCH] Added an initial brainstorm for a more fully-featured tutorial multiplayer game "Battle for Evennia". The idea is to use this as a basis for a series of tutorials on building a relatively complete game in Evennia. The details will probably change, so putting it up for comments from the community. --- contrib/battle_for_evennia/README | 139 ++++++++++++++++++++++++++++++ src/utils/utils.py | 48 ++++------- 2 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 contrib/battle_for_evennia/README diff --git a/contrib/battle_for_evennia/README b/contrib/battle_for_evennia/README new file mode 100644 index 0000000000..51df38901d --- /dev/null +++ b/contrib/battle_for_evennia/README @@ -0,0 +1,139 @@ + +Battle for Evennia +------------------ + +Evennia contrib - Griatch 2012 (WORK IN PROGRESS) + +This is the beginnings of what will be a tutorial for building a +simple yet still reasonably playable and not-quite-bog-standard +starting game in Evennia. The tutorial text itself will eventually be +found from the Dev blog and from the wiki. + +Ideas & Initial Brainstorm +--------------------------- + +This is to be a hack&slash game. Characters fight mobiles and each +other for random loot and better weapons. The highscore is based on +most accumulated gold. They can sell loot to NPC merchants for gold, +and also buy stuff others sold there (spending gold). Characters get +better in the skills they use (no levels). They automatically collect +loot when they kill things, and they cannot drop it (but they can give +it away and, most importantly sell it). Death sends the player back to +a starting position, but gives all but their weakest gear to their +nemesis (they keep all their gold though). + +Inventory of code we need: +- Loot/Equipment lists of Weapons, Armour, Potions and Spells - maybe partly randomly generated. +- Way to spawn in-game objects based on the loot lists +- Character creation module (choose skills, attributes, assigns starting gear) +- 3 Attributes, about 10 skills (some magic?) +- Experience -> skill increase code +- Skill success code - same between PCs as between NPC and PC +- Combat code (twitch-based? Turn-based? Turn based seems easier to balance. Same for NPC vs PC and PC vs PC) +- Mobile code (same for NPCs and enemies) +- 'Give' mechanism (should require consent by receiver) +- No quests, for simplicity. Use gold as a highscore. +- Death respawn mechanism + +Elaboration based on Brainstorm +------------------------------- + + * Loot/Equipment lists and spawn - These could be global-level + dictionaries in a module. Each dictionary gives info such as name, + description and typeclass. Attributes could be set or + randomized. The loot-spawner (probably a handler tied to a dead + mob, treasure chest etc) would use utils.variable_from_module to + extract a random item-template. + + * Characters have 3 attributes: Wile, Strength and Agility. At + creation, they distribute points between them. Wile is used for + bartering with merchants, and using Magic. Strength determines + hand-weapon damage and how heavy armour can be worn. Agility + determines ability to dodge, initiative and using lighter weapons. + Health is based on an average of all three attributes (i.e. all + chars start with the same health). + + * Skills are as follows (may change): + - Long blades (str) ability to hit with swords and also axes. + - Blunt (str) usage of blunt weapons like clubs. Good on armoured foes. + - Spears (agi) usage of spears and hillebards. Bonus on first attack, minus on initiative. + - Daggers (agi) usage of daggers and short blades. Bonus on initiative, bad on armour + - Unarmoured (agi) usage of your fists and feet. Very fast. Bad on armour. + - Dodge (agi) avoiding blows by swift footwork + - Feint (agi) faign attacks to keep the enemy guessing + - Shield (str) absorbing hits with a shielf + - Platemail (str) utilizing heavy armour + - Chainmail (max(str, agi)) utilizing medium armour + - Leather (agi) utilizing light armour + - Barter (wil) barter with merchants for a good price + - Magic (wil) use of single-use magical scrolls to achieve various effects + - Potions (wil) making the best of potions with various effects + - Heal (wil) fixing yourself (or a friend) up between combat. Also judge opponent's wounds. + + * Experience simply rises upon kills and is distributed between the + skills used in the battle (so we need to log this). After N amount of + XP in a skill, that skill automatically goes up one + point. Increasing skills at least N points in 3+ different skills + of a certain type (str, agi, wil) will increase the most trained + Attribute by one point. + + * Skill success is a comparison between the value of a random.gauss + centered around the attacker's skill value vs the result of a + random.gauss centered on the defender's skill. Certain + weapons/defense combinations might be especially effective against + one another (or not). The difference is the base damage, then + adjusted by weapon and armour. In the case of bartering, skill + challenge is between barter skill of both sides; difference + influences the discount/higher price offered for selling/buying. + + * Attacking another player or NPC will start a combat queue. + Combat happens in turns. Each turn each player may do two actions, + picking among the following: + - attack + - parry (with weapon) + - shield + - feint + - dodge + - flee + - block (anti-flee) + Emoting is free in each round, but movement is forbidden unless one + tries to flee (agi challenge, or cancelled by block action). All + combattants involved in a fight submits their actions, then combat + is resolved simultaneously by the code. Order of the two actions + matter, so for example if both attack, neither is trying to parry, + but may hit each other simultaneously. If both parry, shield or + dodge, it means both are dancing about each other. If one feints + and the other parries or dodges, they will have a disadvantage on + the next defensive movement. A successful parry will give the + parried attacker a disadvantage on their next attack. And so on. + Another player may "join the queue" at any time by attacking one of + the combatting PCs. They get to insert their actions together with + the rest on the next round. A round should probably have a timeout + to avoid a Character clogging the queue. + + * Mobiles will use "a global ticker system" where they + subscribe. They act the same way as PCs in combat, except with a + semi- random selection of actions they take (they will probably be + more predictable than PCs). Adding aggressive and passive mobiles + should be straightforward, as well as un-killable ones (merchants). + + * The inventory of a defeated enemy is automatically transferred to + the winner's inventory. If there are many alternative pieces of + equipment, they get to keep the weakest one, otherwise it's all + transferred. There are no limits to carrying except the fear of + losing gear. This should hopefully prevent hoarding of good items. + One can give item(s) to another player - that player must then + conceed to receiving it (use Y/N module in contrib.menusystem). + There is no way of dropping items on the ground; one must either + give them away or sell them for gold to a merchant. + + * Gold is used for buying items from merchants, but is also the + highscore. Whereas sell prices are fixed, buy prices are not fixed + but is based on a percentage of the gold carried, adjusted by the + barter skill (this should defeat inflation quite effectively). Items + sold to merchants are made available for other players to buy. + + * Death means loosing inventory (except weakest item, as mentioned), + but no loss of gold. Otherwise death is cheap - one respawns at a + random starting position (probably needing special-aliased rooms to + use for this - maybe with one-way exits). \ No newline at end of file diff --git a/src/utils/utils.py b/src/utils/utils.py index 374a35ff3b..1b8a9d13fe 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -603,48 +603,38 @@ def mod_import(mod_path, propname=None): return mod_prop return mod -def variable_from_module(modpath, variable, default=None): +def variable_from_module(modpath, variable=None, default=None): """ - Retrieve a given variable from a module. The variable must be - defined globally in the module. This can be used to implement - arbitrary plugin imports in the server. + Retrieve a variable from a module. The variable must be defined + globally in the module. If no variable is given, a random variable + is returned from the module. - If module cannot be imported or variable not found, default + If module cannot be imported or given variable not found, default is returned. """ if not modpath: - return None + return default try: mod = __import__(modpath, fromlist=["None"]) - return mod.__dict__.get(variable, default) except ImportError: return default + if variable: + # try to pick a named variable + return mod.__dict__.get(variable, default) + else: + # random selection + mvars = [val for key, val in mod.__dict__.items() if not key.startswith("_")] + return mvars and random.choice(mvars) def string_from_module(modpath, variable=None, default=None): """ - This is a variation used primarily to get login screens randomly - from a module. - - This obtains a string from a given module python path. Using a - specific variable name will also retrieve non-strings. - - The variable must be global within that module - that is, defined - in the outermost scope of the module. The value of the variable - will be returned. If not found, default is returned. If no variable is - given, a random string variable is returned. - - This is useful primarily for storing various game strings in a - module and extract them by name or randomly. + This is a wrapper for variable_from_module that requires return + value to be a string to pass. It's primarily used by login screen. """ - mod = __import__(modpath, fromlist=[None]) - if variable: - return mod.__dict__.get(variable, default) - else: - mvars = [val for key, val in mod.__dict__.items() - if not key.startswith('_') and isinstance(val, basestring)] - if not mvars: - return default - return mvars[random.randint(0, len(mvars)-1)] + val = variable_from_module(modpath, variable=variable, default=default) + if isinstance(val, basestring): + return val + return default def init_new_player(player): """