diff --git a/app/__init__.py b/app/__init__.py index 0408b19..1f14007 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -18,7 +18,7 @@ app.config['MONSTERS_JSON_PATH'] = MONSTERS_JSON_PATH def init(): from app.routes import coinflip, roll_chance, roll_dice, table_views, encounter, character, \ - ability_check, mental_attack, physical_attack, get_monster + ability_check, mental_attack, physical_attack, get_creature # root namespace restx_api.add_namespace(coinflip.namespace) # dice namespace @@ -27,7 +27,7 @@ def init(): # rules namespace restx_api.add_namespace(table_views.namespace) restx_api.add_namespace(character.namespace) - restx_api.add_namespace(get_monster.namespace) + restx_api.add_namespace(get_creature.namespace) # gameplay namespace restx_api.add_namespace(encounter.namespace) restx_api.add_namespace(ability_check.namespace) diff --git a/app/functions/get_attack_roll_outcome.py b/app/functions/get_attack_roll_outcome.py index 454517d..88c8241 100644 --- a/app/functions/get_attack_roll_outcome.py +++ b/app/functions/get_attack_roll_outcome.py @@ -2,6 +2,8 @@ from app.functions.roll_dices import roll_dices def get_attack_roll_outcome(result, modifier=0): + if modifier is None: + modifier = 0 outcome = 'Unknown' raw_roll = roll_dices(1, 20, False).get('result') needed = result['needed'] diff --git a/app/functions/roll_physical_attack.py b/app/functions/roll_physical_attack.py index 4d6366d..bfc5143 100644 --- a/app/functions/roll_physical_attack.py +++ b/app/functions/roll_physical_attack.py @@ -10,8 +10,10 @@ def roll_physical_attack(dac, modifier=0, awc=None, ahd=None): :param ahd: integer. optional(*). Attacker hit dice. This is required, if weapon attack == False. :return: """ - result = {} + if modifier is None: + modifier = 0 + result = {} if isinstance(awc, int): hit_table = AttackerWeaponClassMatrix() result["needed"] = hit_table.get_attack_score(awc, dac) diff --git a/app/models/__init__.py b/app/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/models.py b/app/models/models.py deleted file mode 100644 index 7ba8562..0000000 --- a/app/models/models.py +++ /dev/null @@ -1,133 +0,0 @@ -from flask_restx import fields - -# Common fields -chartype_field = fields.String( - required=True, - default="human", - description='Character type. Allowed values: "human", "humanoid", "mutant", "cyborg"' -) - -conscore_field = fields.Integer( - required=True, - default=10, - min=3, - max=18, - description='The characters constitution score' -) - -check_model = { - 'ability_score': fields.Integer( - required=True, - default=10, - min=3, - max=21, - description='The score of the ability to check against' - ), - 'multiplier': fields.Integer( - required=True, - default=4, - min=2, max=8, - description='Sets the threshold for the check. In general, the higher the multiplier, the higher the ' - 'likelihood of success. Range: 2 - 7' - ) -} - -# Mutations Model -mutation_model = { - 'conscore': conscore_field, - 'intscore': fields.Integer( - required=True, - default=10, - min=3, - max=21, - description='The characters intelligence score' - ) -} -# Dice model -dice_model = { - 'quantity': fields.Integer(required=True, default=1, description='The number of dice to roll'), - 'geometry': fields.Integer(required=True, default=2, description='The number of sides on each die'), - 'discard_lowest': fields.Boolean(required=False, default=False, description='Drop the lowest score') -} - -# Ability model -ability_model = { - 'chartype': chartype_field, - 'ability': fields.String( - required=False, - default="all", - description='The ability to roll. Not required. Valid options: "m-strength", "p-strength", ' - '"intelligence", "charisma", "constitution", "dexterity", "all". Defaults to "all".'), -} - -# Hp model -hp_model = { - 'chartype': chartype_field, - 'conscore': conscore_field -} - - -pa_model = { - 'weapon_attack': fields.Boolean( - required=True, - default=True, - description='Is the attacker using a weapon? If so, To-Hit is based on weapon class.' - ), - 'dac': fields.Integer( - required=True, - min=1, - max=10, - default=1, - description='The defenders armour class. This is needed for both weapon attacks and non-weapon attacks' - ), - 'awc': fields.Integer( - required=False, - min=1, - max=16, - default=1, - description='The attackers weapon class. This is needed for weapon attacks only.' - ), - 'ahd': fields.Integer( - required=False, - min=1, - max=16, - default=1, - description='The attackers hit dice count. This is needed for non-weapon attacks only.' - ), - 'modifier': fields.Integer( - required=False, - min=-100, - max=100, - default=0, - description='The roll modifier to be applied to the hit roll.' - ) -} - - -ma_model = { - 'ams': fields.Integer( - required=True, - min=3, - max=18, - default=10, - description='Attacker Mental Strength' - ), - 'dms': fields.Integer( - required=True, - min=3, - max=18, - default=10, - description='Defender Mental Strength' - ), - 'modifier': fields.Integer( - required=False, - min=-100, - max=100, - default=0, - description='Modifier For Mental Attack Roll'), -} - -character_model = { - 'chartype': chartype_field, -} - diff --git a/app/routes/ability_check.py b/app/routes/ability_check.py index 710506c..02213d3 100644 --- a/app/routes/ability_check.py +++ b/app/routes/ability_check.py @@ -1,20 +1,27 @@ from flask import request -from flask_restx import Resource, Namespace +from flask_restx import Resource, Namespace, reqparse from app.functions.roll_ability_check import roll_ability_check namespace = Namespace('gameplay', description='Gamma World Rules') +# Define the parser and request args: +parser = reqparse.RequestParser() +parser.add_argument('score', type=int, required=True, + help='The score of the ability to be checked (3 - 21)') +parser.add_argument('multiplier', type=int, required=True, + help='The score multiplier for this check attempt (2 - 10)') @namespace.route('/ability/check') # resolves to: /gameplay/ability/check class AbilityCheck(Resource): - @namespace.doc(params={'score': 'The ability score', 'multiplier': 'Score multiplier for the attempt.'}) + @namespace.expect(parser) def get(self): score = request.args.get('score', type=int) multiplier = request.args.get('multiplier', type=float) - - if not score or not multiplier: - return {'error': 'Both score and multiplier parameters are required.'}, 400 + if score < 3 or score > 21: + return {'message': 'Ability score must be between 3 and 21'}, 400 + if multiplier < 2 or multiplier > 10: + return {'message': 'Multiplier must be between 2 and 10'}, 400 outcome = roll_ability_check(score, multiplier) return outcome, 200 diff --git a/app/routes/character.py b/app/routes/character.py index 0981887..6866ec5 100644 --- a/app/routes/character.py +++ b/app/routes/character.py @@ -1,25 +1,28 @@ from flask import request -from flask_restx import Resource, Namespace +from flask_restx import Resource, Namespace, reqparse from app.functions.build_character_sheet import build_character_sheet namespace = Namespace('rules', description='Gamma World Rules') -VALID_CHARTYPES = ["human", "humanoid", "mutant", "cyborg"] +parser = reqparse.RequestParser() +parser.add_argument('chartype', type=str, default='Human', + help='The Character Type for the new character (human, humanoid, mutant, cyborg)') @namespace.route('/character') # resolves to: /rules/character class GenerateCharacter(Resource): - @namespace.doc(params={'chartype': 'The Character Type for the new character'}) + @namespace.expect(parser) def get(self): + valid_chartypes = ["human", "humanoid", "mutant", "cyborg"] chartype = request.args.get('chartype', default='Human', type=str) if chartype: - if chartype.lower() in VALID_CHARTYPES: + if chartype.lower() in valid_chartypes: return build_character_sheet(chartype.lower()), 200 else: return { 'error': 'Invalid character type provided.', - 'valid_chartypes': VALID_CHARTYPES + 'valid_chartypes': valid_chartypes }, 400 else: - return {'error': 'No character type provided', 'valid_chartypes': VALID_CHARTYPES}, 400 + return {'error': 'No character type provided', 'valid_chartypes': valid_chartypes}, 400 diff --git a/app/routes/encounter.py b/app/routes/encounter.py index 56204d6..655b632 100644 --- a/app/routes/encounter.py +++ b/app/routes/encounter.py @@ -1,26 +1,28 @@ from flask import request -from flask_restx import Resource, Namespace - +from flask_restx import Resource, Namespace, reqparse from app.functions.roll_encounter import roll_encounter namespace = Namespace('gameplay', description='Gamma World Game Play') -VALID_TERRAINS = ["clear", "mountains", "forest", "desert", "watery", "ruins", "deathlands"] +parser = reqparse.RequestParser() +parser.add_argument('terrain', type=str, default='clear', + help='The terrain being traversed. ("clear", "mountains", "forest", "desert",' + '"watery", "ruins", "deathlands")') @namespace.route('/encounter') # resolves to: /gameplay/encounter class RollEncounter(Resource): - @namespace.doc(params={'terrain': 'The terrain type for the encounter'}) + @namespace.expect(parser) def get(self): - terrain = request.args.get('terrain', default=None, type=str) - + terrain = request.args.get('terrain', default="clear", type=str) + valid_terrains = ["clear", "mountains", "forest", "desert", "watery", "ruins", "deathlands"] if terrain: - if terrain.lower() in VALID_TERRAINS: + if terrain.lower() in valid_terrains: return roll_encounter(terrain.lower()), 200 else: return { 'error': 'Invalid terrain type provided.', - 'valid_terrains': VALID_TERRAINS + 'valid_terrains': valid_terrains }, 400 else: - return {'error': 'No terrain type provided', 'valid_terrains': VALID_TERRAINS}, 400 + return {'error': 'No terrain type provided', 'valid_terrains': valid_terrains}, 400 diff --git a/app/routes/get_creature.py b/app/routes/get_creature.py new file mode 100644 index 0000000..eb31429 --- /dev/null +++ b/app/routes/get_creature.py @@ -0,0 +1,18 @@ +from flask import request +from flask_restx import Resource, Namespace, reqparse +from app.tables.creature import Creatures + +namespace = Namespace('rules', description='Gamma World Rules') +parser = reqparse.RequestParser() +parser.add_argument('creature', type=str, help='the name of a Gamma World creature to search for.') + + +@namespace.route('/creature') # resolves to: /rules/creature +class RollEncounter(Resource): + @namespace.expect(parser) + def get(self): + creature = request.args.get('creature', default=None, type=str) + if creature is None: + return {'error': 'Provide the name of a Gamma World creature to search for'}, 400 + creatures = Creatures() + return creatures.get_creature(creature), 200 diff --git a/app/routes/get_monster.py b/app/routes/get_monster.py deleted file mode 100644 index ae55e67..0000000 --- a/app/routes/get_monster.py +++ /dev/null @@ -1,19 +0,0 @@ -from flask import request -from flask_restx import Resource, Namespace - -from app.tables.monsters import Monsters - -namespace = Namespace('rules', description='Gamma World Rules') - - -@namespace.route('/creature') # resolves to: /gameplay/encounter -class RollEncounter(Resource): - @namespace.doc(params={'creature': 'The terrain type for the encounter'}) - def get(self): - creature = request.args.get('creature', default=None, type=str) - - if creature is None: - return {'error': 'Provide the name of a Gamma World creature to search for'}, 400 - - monsters = Monsters() - return monsters.get_monster(creature), 200 diff --git a/app/routes/mental_attack.py b/app/routes/mental_attack.py index 7fe089f..74d9f7a 100644 --- a/app/routes/mental_attack.py +++ b/app/routes/mental_attack.py @@ -1,19 +1,30 @@ from flask import request -from flask_restx import Resource, Namespace +from flask_restx import Resource, Namespace, reqparse from app.functions.roll_mental_attack import roll_mental_attack namespace = Namespace('gameplay', description='Gamma World Rules') +# doc(params={'ams': 'Attacker Mental Strength', +# 'dms': 'Defender Mental Strength', +# 'modifier': 'Roll Modifier'}) + +parser = reqparse.RequestParser() +parser.add_argument('ams', type=int, required=True, help='Attacker Mental Strength (1 - 18)') +parser.add_argument('dms', type=int, required=True, help='Defender Mental Strength (1 - 18)') +parser.add_argument('modifier', type=int, required=True, help='Roll Modifier') + @namespace.route('/attack/mental') # resolves to: /gameplay/attack/mental class MentalAttack(Resource): - @namespace.doc(params={'ams': 'Attacker Mental Strength', - 'dms': 'Defender Mental Strength', - 'modifier': 'Roll Modifier'}) + @namespace.expect(parser) def get(self): ams = request.args.get('ams', type=int) dms = request.args.get('dms', type=int) modifier = request.args.get('modifier', type=int) - # Validate params here + if ams < 1 or ams > 18: + return {'message': 'Attacker Mental Strength must be between 1 and 18'}, 400 + if dms < 1 or dms > 18: + return {'message': 'Defender Mental Strength must be between 1 and 18'}, 400 + return roll_mental_attack(ams, dms, modifier), 200 diff --git a/app/routes/physical_attack.py b/app/routes/physical_attack.py index 0429204..dd7833a 100644 --- a/app/routes/physical_attack.py +++ b/app/routes/physical_attack.py @@ -1,18 +1,20 @@ from flask import request -from flask_restx import Resource, Namespace +from flask_restx import Resource, Namespace, reqparse from app.functions.roll_physical_attack import roll_physical_attack namespace = Namespace('gameplay', description='Gamma World Rules') +parser = reqparse.RequestParser() +parser.add_argument('dac', type=int, required=True, help='REQUIRED: Defender Armour Class. Needed for both attacks') +parser.add_argument('awc', type=int, help='OPTIONAL(*): Attacker Weapon Class. Only needed for weapon attacks') +parser.add_argument('ahd', type=int, help='OPTIONAL(*): Attacker Hit Dice. Only needed for non-weapon attacks') +parser.add_argument('modifier', type=int, help='OPTIONAL: Roll Modifier') + @namespace.route('/attack/physical') # resolves to: /gameplay/attack/physical class PhysicalAttack(Resource): - @namespace.doc( - params={'dac': 'REQUIRED: Needed for both attacks', - 'modifier': 'OPTIONAL: Roll Modifier', - 'awc': 'OPTIONAL(*): Attacker Weapon Class. Only needed for weapon attacks', - 'ahd': 'OPTIONAL(*): Attacker Hit Dice. Only needed for non-weapon attacks'}) + @namespace.expect(parser) def get(self): dac = request.args.get('dac', type=int) awc = request.args.get('awc', type=int) diff --git a/app/routes/roll_dice.py b/app/routes/roll_dice.py index e8ae163..7cfb653 100644 --- a/app/routes/roll_dice.py +++ b/app/routes/roll_dice.py @@ -1,24 +1,25 @@ -from flask_restx import Resource, Namespace +from flask_restx import Resource, Namespace, reqparse from app.functions.roll_dices import roll_dices -from app.models.models import dice_model -from app.schemas.schemas import DiceSchema namespace = Namespace('dice', description='Dice Operations') -dice_model = namespace.model('Dice', dice_model) -dice_schema = DiceSchema() + +# Define the parser and request args: +parser = reqparse.RequestParser() +parser.add_argument('quantity', type=int, required=True, help='Quantity of dice to roll') +parser.add_argument('geometry', type=int, required=True, help='Number of faces on the dice') +parser.add_argument('discard_lowest', type=bool, required=True, help='Whether to discard lowest roll') @namespace.route('/') # resolves to: /dice class RollDice(Resource): - @namespace.expect(dice_model) - def post(self): - data = namespace.payload - errors = dice_schema.validate(data) - if errors: - return errors, 400 - quantity = data.get('quantity') - geometry = data.get('geometry') - discard_lowest = data.get('discard_lowest') - if quantity is None or geometry is None: - return {"message": "Required dice data not provided"}, 400 + @namespace.expect(parser) + def get(self): + args = parser.parse_args() + quantity = args['quantity'] + geometry = args['geometry'] + discard_lowest = args['discard_lowest'] + if quantity < 1 or quantity > 100: + return {'message': 'Quantity must be between 1 and 100'}, 400 + if geometry < 3 or geometry > 100: + return {'message': 'Geometry must be between 3 and 100'}, 400 return roll_dices(quantity, geometry, discard_lowest), 200 diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/schemas/schemas.py b/app/schemas/schemas.py deleted file mode 100644 index 163aa9c..0000000 --- a/app/schemas/schemas.py +++ /dev/null @@ -1,130 +0,0 @@ -from marshmallow import Schema, fields, validate - -chartype_field = fields.String( - required=True, - validate=validate.OneOf(["human", "humanoid", "mutant", "cyborg"]), - description='The characters type of being' -) - -conscore_field = fields.Integer( - required=True, - default=10, - validate=validate.Range(min=3, max=18), - description='The constitution score of the character' -) - - -class MutationSchema(Schema): - conscore = conscore_field - intscore = fields.Integer( - required=True, - validate=validate.Range(min=3, max=18), - description='The characters intelligence score' - ) - - -class HPSchema(Schema): - chartype = chartype_field - conscore = conscore_field - - -class DiceSchema(Schema): - quantity = fields.Int( - required=True, - default=1, - validate=validate.Range(min=1), - description='The number of dice to roll' - ) - geometry = fields.Int( - required=True, - default=2, - validate=validate.Range(min=2), - description='The number of sides on each die' - ) - discard_lowest = fields.Bool(required=True, default=False, description='Drop the lowest score') - - -class CharacterSchema(Schema): - chartype = chartype_field - - -class AbilitySchema(Schema): - chartype = chartype_field - ability = fields.String( - required=False, - default="generic", - validate=validate.OneOf( - ["m-strength", "p-strength", "intelligence", "charisma", "constitution", "dexterity", "all"]), - description='One of the six character attributes from the character sheet' - ) - - -class CheckSchema(Schema): - ability_score = fields.Integer( - required=True, - default=10, - validate=validate.Range(min=3, max=21), - description='The score of the ability to be checked against' - ) - multiplier = fields.Integer( - required=True, - default=4, - validate=validate.Range(min=2, max=8), - description='Sets the threshold for the check. In general, the higher the multiplier, the higher the ' - 'likelihood of success. Range: 2 - 7' - ) - - -class MentalAttackSchema(Schema): - ams = fields.Integer( - required=True, - validate=validate.Range(min=3, max=18), - description='The Attackers Mental Strength' - ) - dms = fields.Integer( - required=True, - validate=validate.Range(min=3, max=18), - description='The Defenders Mental Strength' - ) - modifier = fields.Integer( - required=False, - default=0, - validate=validate.Range(min=-100, max=100), - description='Roll modifier for mental attack' - ) - - -class PhysicalAttackSchema(Schema): - weapon_attack = fields.Boolean( - required=True, - default=True, - description="Is the attacker using a weapon? If so, To-Hit is based on weapon class." - ) - dac = fields.Integer( - required=True, - min=1, - max=10, - default=1, - description='The defenders armour class. This is needed for both weapon attacks and non-weapon attacks' - ) - awc = fields.Integer( - required=False, - min=1, - max=16, - default=1, - description='The attackers weapon class. This is needed for weapon attacks only.' - ) - ahd = fields.Integer( - required=False, - min=1, - max=16, - default=1, - description='The attackers hit dice count. This is needed for non-weapon attacks only.' - ) - modifier = fields.Integer( - required=False, - min=-100, - max=100, - default=0, - description='The roll modifier to be applied to the hit roll.' - ) diff --git a/app/tables/creature.py b/app/tables/creature.py new file mode 100644 index 0000000..318708b --- /dev/null +++ b/app/tables/creature.py @@ -0,0 +1,19 @@ +import json + + +class Creatures: + creatures = None + + @staticmethod + def load_creature_data(): + """Loads the monsters data from a JSON file""" + with open('app/tables/creatures.json') as f: + Creatures.creatures = json.load(f) + + def __init__(self): + if not Creatures.creatures: + self.load_creature_data() + + def get_creature(self, creature_name): + """Returns the dictionary of the specified creature.""" + return self.creatures.get(creature_name) diff --git a/app/tables/monsters.json b/app/tables/creatures.json similarity index 100% rename from app/tables/monsters.json rename to app/tables/creatures.json diff --git a/app/tables/monsters.py b/app/tables/monsters.py deleted file mode 100644 index 7e12163..0000000 --- a/app/tables/monsters.py +++ /dev/null @@ -1,28 +0,0 @@ -import json - - -class Monsters: - monsters = None - - @staticmethod - def load_monsters_data(): - """Loads the monsters data from a JSON file""" - with open('app/tables/monsters.json') as f: - Monsters.monsters = json.load(f) - - def __init__(self): - if not Monsters.monsters: - self.load_monsters_data() - - def get_monster(self, monster_name): - """Returns the dictionary of the specified monster.""" - return self.monsters.get(monster_name) - - def add_monster(self, monster_name, attributes): - """Adds a new monster to the monsters dictionary.""" - self.monsters[monster_name] = attributes - - def remove_monster(self, monster_name): - """Removes a monster from the monsters dictionary.""" - if monster_name in self.monsters: - del self.monsters[monster_name] diff --git a/config.py b/config.py index fb5bb62..1de0899 100644 --- a/config.py +++ b/config.py @@ -3,4 +3,4 @@ import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(BASE_DIR, 'app/tables') -MONSTERS_JSON_PATH = os.path.join(DATA_DIR, 'monsters.json') +MONSTERS_JSON_PATH = os.path.join(DATA_DIR, 'creatures.json')