From 1e862d3b6150cd4c8407279651e2a9d269d19b7a Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Sun, 23 Jun 2024 18:30:13 +0100 Subject: [PATCH] refactored character and ability generation;stubbed out attack matrices --- app.py | 159 ++++++++++++++++++++++++++++++++------------------ cybermods.py | 33 +++++++++++ models.py | 9 +-- physattack.py | 12 ++++ schemas.py | 27 +++++---- 5 files changed, 166 insertions(+), 74 deletions(-) create mode 100644 cybermods.py create mode 100644 physattack.py diff --git a/app.py b/app.py index b141228..4f82036 100644 --- a/app.py +++ b/app.py @@ -3,11 +3,12 @@ from flask_cors import CORS from flask_restx import Api, Resource from models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model -from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema +from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema from encounters import EncounterTable from mentattack import MentalAttackMatrix from mutations import Mutations +from cybermods import CyberMods import random @@ -26,7 +27,7 @@ character = api.namespace('character', description='Character operations') encounter = api.namespace('encounter', description='Encounter operations') ability_model = ability.model('Ability', ability_model) -# TODO: Add ability schema +ability_schema = AbilitySchema() hp_model = hp.model('HP', hp_model) # TODO: Add hitpoint schema @@ -65,12 +66,14 @@ class RollAbility(Resource): @ability.expect(ability_model) def post(self): data = api.payload + errors = ability_schema.validate(data) + if errors: + return errors, 400 + chartype = data.get('chartype') - if chartype == 'human': - geometry = 8 - else: - geometry = 6 - return roll_dices(4, geometry, True), 200 + attribute = data.get('ability') + + return roll_ability_scores(chartype, attribute), 200 @api.route('/roll/hp', methods=['POST']) @@ -180,24 +183,22 @@ class GenerateCharacter(Resource): if errors: return errors, 400 chartype = data.get('chartype') - char_emphasis = data.get('emphasis') character_sheet = {} + ability_scores = roll_ability_scores(chartype) + character_sheet['abilities'] = ability_scores + character_sheet['hp'] = roll_hp(chartype, ability_scores['constitution']) + character_sheet['gold'] = roll_dices(4, 4, False).get('result') * 10 + character_sheet['domar'] = roll_dices(2, 4, False).get('result') * 5 - if chartype == 'human': - geometry = 8 - else: - geometry = 6 - scores = roll_ability_scores(chartype) - assigned_abilities = assign_ability_scores(scores, char_emphasis) - character_sheet['abilities'] = assigned_abilities - character_sheet['hp'] = roll_hp(chartype, assigned_abilities['constitution']) - character_sheet['gold'] = roll_dices(4, 4, False).get('result') - character_sheet['domar'] = roll_dices(2, 4, False).get('result') - - if chartype == 'mutant': + if chartype == 'humanoid' or chartype == 'mutant': character_sheet['mutations'] = ( - roll_mutations(assigned_abilities['constitution'], assigned_abilities['intelligence']) + roll_mutations(ability_scores['constitution'], ability_scores['intelligence']) + ) + + if chartype == 'cyborg': + character_sheet['cybermods'] = ( + roll_cybermods() ) return character_sheet, 200 @@ -234,43 +235,30 @@ def roll_dices(dice_count, dice_sides, discard_lowest): return result -def roll_ability_scores(chartype): - if chartype == 'human': - geometry = 8 +def roll_ability_scores(chartype, attribute=None): + if attribute is None or attribute == "all": + ability_list = ['p-strength', 'm-strength', 'constitution', 'dexterity', 'charisma', 'intelligence'] else: - geometry = 6 + ability_list = [attribute] - scores = [] - for _ in range(6): - scores.append(roll_dices(4, geometry, True)['result']) - - return scores - - -def assign_ability_scores(scores, char_emphasis): ability_scores = {} - if char_emphasis == 'physical': - ability_list = ['p-strength', 'constitution', 'dexterity', - 'charisma', 'intelligence', 'm-strength'] - for ab in ability_list: - max_score = max(scores) - ability_scores[ab] = max_score - scores.remove(max_score) - elif char_emphasis == 'mental': - ability_list = ['m-strength', 'intelligence', 'charisma', - 'constitution', 'dexterity', 'p-strength'] - for ab in ability_list: - max_score = max(scores) - ability_scores[ab] = max_score - scores.remove(max_score) - - else: - ability_list = ['p-strength', 'constitution', 'dexterity', - 'charisma', 'intelligence', 'm-strength'] - random.shuffle(scores) - for ab, score in zip(ability_list, scores): - ability_scores[ab] = score + for abil in ability_list: + if ((chartype == 'human') and + ((abil == 'intelligence') or + (abil == 'charisma') or + (abil == 'constitution'))): + roll = roll_dices(4, 6, False) # humans are special + if (abil == 'constitution') and roll["result"] > 18: + roll["capped"] = True + roll["result"] = 18 + if (abil == 'intelligence' or abil == 'charisma') and roll["result"] > 21: + roll["capped"] = True + roll["result"] = 21 + ability_scores[abil] = roll["result"] + else: + roll = roll_dices(4, 6, True) # nothing else is special + ability_scores[abil] = roll["result"] return ability_scores @@ -283,6 +271,57 @@ def roll_hp(chartype, conscore): return roll_dices(conscore, geometry, False).get('result') +def roll_cybermods(): + """ + Cybermods are similar to mutations, except that they are artificial changes to the body, + where mutations are 'natural' changes to the body. Fewer cybermods are possible than with + mutations. So, you make one roll of 1d4, and then parse up the score between the two + types of mods, as you see fit. If no emphasis is specified, the score will be split as evenly + as possible. If the score is 1, then a physical mod will be the default. + :return: table of cybermods (in json format) + """ + cybermods_table = {} + + cybermods_count = roll_dices(1, 4, False)["result"] + phys_cnt = split_number(cybermods_count)[0] + ment_cnt = split_number(cybermods_count)[1] + + mental_cybermods_scores = [] + physical_cybermods_scores = [] + + for _ in range(ment_cnt): + mscore = roll_dices(1, 10, False).get('result') + if mscore in mental_cybermods_scores: + mscore = roll_dices(1, 10, False).get('result') + mental_cybermods_scores.append(mscore) + + for _ in range(phys_cnt): + pscore = roll_dices(1, 10, False).get('result') + if pscore in physical_cybermods_scores: + pscore = roll_dices(1, 10, False).get('result') + physical_cybermods_scores.append(pscore) + + cyb = CyberMods() + mcyb = [] + pcyb = [] + + for score in mental_cybermods_scores: + modification = cyb.get_mental_cybermod(score) + mcyb.append(modification) + + if len(mcyb) != 0: + cybermods_table['mental'] = mcyb + + for score in physical_cybermods_scores: + modification = cyb.get_physical_cybermod(score) + pcyb.append(modification) + + if len(pcyb) != 0: + cybermods_table['physical'] = pcyb + + return cybermods_table + + def roll_mutations(conscore, intscore): """ :param conscore: modifier for physical mutation determination @@ -301,8 +340,7 @@ def roll_mutations(conscore, intscore): for _ in range(mental_mutations_cnt): mscore = roll_dices(1, 100, False).get('result') + intscore if mscore in mental_mutations_scores: - mscore = mscore - 3 - + mscore = roll_dices(1, 100, False).get('result') + intscore if mscore > 100: mscore = 100 mental_mutations_scores.append(mscore) @@ -310,8 +348,7 @@ def roll_mutations(conscore, intscore): for _ in range(physical_mutations_cnt): pscore = roll_dices(1, 100, False).get('result') + conscore if pscore in physical_mutations_scores: - pscore = pscore - 3 - + pscore = roll_dices(1, 100, False).get('result') + conscore if pscore > 100: pscore = 100 physical_mutations_scores.append(pscore) @@ -335,5 +372,11 @@ def roll_mutations(conscore, intscore): return mutations_table +def split_number(n): + first_split = n // 2 # this will split the number in two equal parts, if it is even + second_split = n % 2 + first_split # this will add one to one part if the number is odd + return first_split, second_split + + if __name__ == '__main__': app.run() diff --git a/cybermods.py b/cybermods.py new file mode 100644 index 0000000..8d39404 --- /dev/null +++ b/cybermods.py @@ -0,0 +1,33 @@ +import pandas as pd + + +class CyberMods(object): + def __init__(self): + self.raw_cybermods_table = { + 'Score': list(range(1, 11)), + 'Physical': ['Replacement Limb', 'Replacement Sense Organ', 'Attribute Enhancement', + 'Additional Limb', 'Additional Sense Organ', 'Attachment Interface', + 'Energy Emitter', 'Energy Absorption', 'Nano-tech Collective', 'YOU INVENT ONE'], + 'Mental': ['Replacement Brain', 'Transmitter', 'Receiver', 'Energy Detection', 'Knowledge Base', + 'Pacify Android', 'Control Machine', 'Tech Recognition', 'Fabrication', 'YOU INVENT ONE'] + } + + self.cybermods_table = pd.DataFrame(self.raw_cybermods_table) + + def get_physical_cybermod(self, score): + cybermod = self.cybermods_table.loc[self.cybermods_table['Score'] == score, 'Physical'] + return cybermod.iloc[0] if not cybermod.empty else None + + def get_mental_cybermod(self, score): + cybermod = self.cybermods_table.loc[self.cybermods_table['Score'] == score, 'Mental'] + return cybermod.iloc[0] if not cybermod.empty else None + + def get_table_shape(self): + return self.cybermods_table.shape + + def get_table_row_count(self): + return self.cybermods_table.shape[0] + + def get_table_column_count(self): + return self.cybermods_table.shape[1] + diff --git a/models.py b/models.py index 66f45f7..ca7dedc 100644 --- a/models.py +++ b/models.py @@ -3,7 +3,7 @@ from flask_restx import fields # Common fields chartype_field = fields.String( required=False, default="human", - description='Character type. Allowed values: "human", "mutant", "android", "robot"') + description='Character type. Allowed values: "human", "humanoid", "mutant", "cyborg", "android"') # Dice model dice_model = { @@ -14,7 +14,8 @@ dice_model = { # Ability model ability_model = { - 'chartype': chartype_field + 'chartype': chartype_field, + 'ability': fields.String(required=False, description='The ability to roll. Not required. Defaults "generic"'), } # Hp model @@ -31,10 +32,6 @@ ma_model = { character_model = { 'chartype': chartype_field, - 'emphasis': fields.String( - required=False, - default="random", - description='The attribute emphasis of your character. Choices: "physical", "mental", "random"') } encounter_model = { diff --git a/physattack.py b/physattack.py new file mode 100644 index 0000000..c91a633 --- /dev/null +++ b/physattack.py @@ -0,0 +1,12 @@ +import pandas as pd +import numpy as np + + +class WeaponClassAttackMatrix: + def __init__(self): + data = {} + + +class HitDiceAttackMatrix: + def __init__(self): + data = {} diff --git a/schemas.py b/schemas.py index 8e4886a..acbb21d 100644 --- a/schemas.py +++ b/schemas.py @@ -1,5 +1,11 @@ from marshmallow import Schema, fields, validate +chartype_field = fields.String( + required=True, + validate=validate.OneOf(["human", "humanoid", "mutant", "cyborg", "android"]), + description='The characters type of being' +) + class DiceSchema(Schema): quantity = fields.Int(required=True, validate=validate.Range(min=1), description='The number of dice to roll') @@ -8,15 +14,17 @@ class DiceSchema(Schema): class CharacterSchema(Schema): - chartype = fields.String( - required=True, - validate=validate.OneOf(["human", "mutant", "android", "robot"]), - description='The characters type of being' - ) - emphasis = fields.String( - required=True, - validate=validate.OneOf(["physical", "mental", "random"]), - description='Valid inputs: physical, mental, random' + 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' ) @@ -45,4 +53,3 @@ class MentalAttackSchema(Schema): validate=validate.Range(min=-100, max=100), description='Roll modifier for mental attack' ) -