2024-06-22 00:05:00 +00:00
|
|
|
from flask import Flask
|
2024-06-22 21:29:19 +00:00
|
|
|
from flask_cors import CORS
|
2024-06-22 13:22:39 +00:00
|
|
|
from flask_restx import Api, Resource
|
2024-06-23 11:13:42 +00:00
|
|
|
|
|
|
|
from models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model
|
2024-06-23 17:30:13 +00:00
|
|
|
from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema
|
2024-06-22 22:56:55 +00:00
|
|
|
|
|
|
|
from encounters import EncounterTable
|
2024-06-23 11:13:42 +00:00
|
|
|
from mentattack import MentalAttackMatrix
|
2024-06-22 18:32:17 +00:00
|
|
|
from mutations import Mutations
|
2024-06-23 17:30:13 +00:00
|
|
|
from cybermods import CyberMods
|
2024-06-23 11:57:26 +00:00
|
|
|
|
2024-06-21 21:49:11 +00:00
|
|
|
import random
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
2024-06-22 21:29:19 +00:00
|
|
|
CORS(app)
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
app.config.SWAGGER_UI_JSONEDITOR = True
|
|
|
|
app.config['SWAGGER_UI_JSONEDITOR'] = True
|
|
|
|
api = Api(app, version='1.0', title='Gamma World Dice', description='Rolled Dice As A Service')
|
|
|
|
|
|
|
|
dice = api.namespace('dice', description='Dice operations')
|
|
|
|
ability = api.namespace('ability', description='Ability operations')
|
|
|
|
hp = api.namespace('hp', description='HP operations')
|
2024-06-23 11:13:42 +00:00
|
|
|
ma = api.namespace('ma', description='Mental Attack operations')
|
2024-06-22 13:22:39 +00:00
|
|
|
character = api.namespace('character', description='Character operations')
|
2024-06-22 22:56:55 +00:00
|
|
|
encounter = api.namespace('encounter', description='Encounter operations')
|
2024-06-22 00:05:00 +00:00
|
|
|
|
2024-06-22 10:29:05 +00:00
|
|
|
ability_model = ability.model('Ability', ability_model)
|
2024-06-23 17:30:13 +00:00
|
|
|
ability_schema = AbilitySchema()
|
2024-06-22 10:29:05 +00:00
|
|
|
|
2024-06-23 11:57:26 +00:00
|
|
|
hp_model = hp.model('HP', hp_model)
|
|
|
|
# TODO: Add hitpoint schema
|
2024-06-22 10:29:05 +00:00
|
|
|
|
2024-06-23 11:57:26 +00:00
|
|
|
dice_model = dice.model('Dice', dice_model)
|
2024-06-22 10:29:05 +00:00
|
|
|
dice_schema = DiceSchema()
|
2024-06-22 00:05:00 +00:00
|
|
|
|
2024-06-23 11:57:26 +00:00
|
|
|
ma_model = ma.model('MA', ma_model)
|
|
|
|
ma_schema = MentalAttackSchema()
|
2024-06-22 00:05:00 +00:00
|
|
|
|
2024-06-23 11:57:26 +00:00
|
|
|
character_model = character.model('Character', character_model)
|
2024-06-22 13:22:39 +00:00
|
|
|
character_schema = CharacterSchema()
|
|
|
|
|
2024-06-23 11:57:26 +00:00
|
|
|
encounter_model = encounter.model('Encounter', encounter_model)
|
2024-06-22 22:56:55 +00:00
|
|
|
encounter_schema = EncounterSchema()
|
|
|
|
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
@api.route('/roll/dice', methods=['POST'])
|
|
|
|
class RollDice(Resource):
|
|
|
|
@dice.expect(dice_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
2024-06-22 10:29:05 +00:00
|
|
|
errors = dice_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
2024-06-22 00:05:00 +00:00
|
|
|
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
|
|
|
|
return roll_dices(quantity, geometry, discard_lowest), 200
|
|
|
|
|
|
|
|
|
|
|
|
@api.route('/roll/ability', methods=['POST'])
|
|
|
|
class RollAbility(Resource):
|
|
|
|
@ability.expect(ability_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
2024-06-23 17:30:13 +00:00
|
|
|
errors = ability_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
chartype = data.get('chartype')
|
2024-06-23 17:30:13 +00:00
|
|
|
attribute = data.get('ability')
|
|
|
|
|
|
|
|
return roll_ability_scores(chartype, attribute), 200
|
2024-06-22 00:05:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
@api.route('/roll/hp', methods=['POST'])
|
|
|
|
class RollHP(Resource):
|
|
|
|
@hp.expect(hp_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
chartype = data.get('chartype')
|
|
|
|
conscore = data.get('conscore')
|
|
|
|
if conscore is None:
|
|
|
|
return {"message": "A constitution score is required"}, 400
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
return roll_dices(conscore, geometry, False), 200
|
|
|
|
|
|
|
|
|
2024-06-22 22:56:55 +00:00
|
|
|
@api.route('/roll/encounter', methods=['POST'])
|
|
|
|
class RollEncounter(Resource):
|
|
|
|
@encounter.expect(encounter_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
errors = encounter_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
|
|
|
|
|
|
|
terrain = data.get('terrain').lower()
|
|
|
|
roll = roll_dices(1, 20, False).get('result')
|
|
|
|
et = EncounterTable()
|
|
|
|
creature = et.get_encounter(roll, terrain)
|
|
|
|
if creature is None:
|
|
|
|
creature = "All clear!"
|
|
|
|
|
|
|
|
return {"encounter": creature}, 200
|
|
|
|
|
|
|
|
|
2024-06-23 11:13:42 +00:00
|
|
|
@api.route('/roll/attack/mental', methods=['POST'])
|
|
|
|
class RollMentalAttack(Resource):
|
|
|
|
@ma.expect(ma_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
errors = ma_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
|
|
|
|
|
|
|
ams = data.get('ams')
|
|
|
|
dms = data.get('dms')
|
|
|
|
modifier = data.get('modifier')
|
|
|
|
|
|
|
|
result = {}
|
|
|
|
mam = MentalAttackMatrix()
|
|
|
|
needed = mam.get_attack_score(ams, dms)
|
|
|
|
result["needed"] = needed
|
|
|
|
outcome = None
|
|
|
|
if needed < 2:
|
|
|
|
outcome = "Automatic Success!"
|
|
|
|
elif needed > 20:
|
|
|
|
outcome = "Absolutely No Chance!"
|
|
|
|
else:
|
|
|
|
raw_roll = roll_dices(1, 20, False).get('result')
|
|
|
|
if modifier != 0:
|
|
|
|
result["original-roll"] = raw_roll
|
|
|
|
result["modifier"] = modifier
|
|
|
|
rolled = raw_roll + modifier # negative modifiers will subtract themselves naturally
|
|
|
|
result["adjusted-roll"] = rolled
|
|
|
|
else:
|
|
|
|
rolled = raw_roll
|
|
|
|
result["original-roll"] = rolled
|
|
|
|
|
|
|
|
if (rolled >= 20) and (needed <= 16):
|
|
|
|
outcome = "Devastating Success!"
|
|
|
|
elif needed <= rolled:
|
|
|
|
outcome = "Attack Successful!"
|
|
|
|
elif needed > rolled:
|
|
|
|
outcome = "Attack Failed!"
|
|
|
|
|
|
|
|
result["outcome"] = outcome
|
|
|
|
|
|
|
|
return result, 200
|
|
|
|
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
@api.route('/roll/tohit', methods=['GET'])
|
|
|
|
class RollToHit(Resource):
|
|
|
|
def get(self):
|
|
|
|
return roll_dices(1, 20, False), 200
|
|
|
|
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
@api.route('/roll/chance', methods=['GET'])
|
|
|
|
class RollChance(Resource):
|
|
|
|
def get(self):
|
|
|
|
return roll_dices(1, 100, False), 200
|
|
|
|
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
@api.route('/coinflip', methods=['GET'])
|
|
|
|
class RollCoinflip(Resource):
|
2024-06-22 00:05:00 +00:00
|
|
|
def get(self):
|
2024-06-22 13:22:39 +00:00
|
|
|
return random.choice(['Heads', 'Tails']), 200
|
|
|
|
|
|
|
|
|
|
|
|
@api.route('/character/generate')
|
|
|
|
class GenerateCharacter(Resource):
|
|
|
|
@character.expect(character_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
errors = character_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
|
|
|
chartype = data.get('chartype')
|
|
|
|
|
|
|
|
character_sheet = {}
|
2024-06-23 23:47:38 +00:00
|
|
|
if chartype == 'mutant':
|
|
|
|
character_sheet['animal-stock'] = (roll_mutant_animal())
|
2024-06-23 17:30:13 +00:00
|
|
|
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
|
2024-06-22 13:22:39 +00:00
|
|
|
|
2024-06-23 17:30:13 +00:00
|
|
|
if chartype == 'humanoid' or chartype == 'mutant':
|
2024-06-22 18:32:17 +00:00
|
|
|
character_sheet['mutations'] = (
|
2024-06-23 17:30:13 +00:00
|
|
|
roll_mutations(ability_scores['constitution'], ability_scores['intelligence'])
|
|
|
|
)
|
|
|
|
|
|
|
|
if chartype == 'cyborg':
|
|
|
|
character_sheet['cybermods'] = (
|
|
|
|
roll_cybermods()
|
2024-06-22 18:32:17 +00:00
|
|
|
)
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
return character_sheet, 200
|
2024-06-21 21:49:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
def roll_dices(dice_count, dice_sides, discard_lowest):
|
|
|
|
roll_results = []
|
2024-06-22 10:29:05 +00:00
|
|
|
discarded = []
|
2024-06-21 21:49:11 +00:00
|
|
|
for _ in range(dice_count):
|
|
|
|
roll_results.append(random.randint(1, dice_sides))
|
|
|
|
if discard_lowest and len(roll_results) > 0:
|
2024-06-22 10:29:05 +00:00
|
|
|
discarded.append(min(roll_results))
|
2024-06-21 21:49:11 +00:00
|
|
|
roll_results.remove(min(roll_results))
|
|
|
|
|
2024-06-22 10:29:05 +00:00
|
|
|
if discarded:
|
2024-06-22 13:22:39 +00:00
|
|
|
net_dice_count = dice_count - 1
|
|
|
|
mnemonic = str(dice_count) + "(-1)d" + str(dice_sides)
|
2024-06-22 10:29:05 +00:00
|
|
|
else:
|
2024-06-22 13:22:39 +00:00
|
|
|
mnemonic = str(dice_count) + 'd' + str(dice_sides)
|
2024-06-22 10:29:05 +00:00
|
|
|
net_dice_count = dice_count
|
|
|
|
|
2024-06-21 21:49:11 +00:00
|
|
|
result = {
|
2024-06-22 10:29:05 +00:00
|
|
|
'dice-set': {
|
|
|
|
'mnemonic': mnemonic,
|
|
|
|
'min-roll': net_dice_count,
|
2024-06-22 13:22:39 +00:00
|
|
|
'max-roll': dice_sides * net_dice_count
|
2024-06-22 10:29:05 +00:00
|
|
|
},
|
2024-06-21 21:49:11 +00:00
|
|
|
'rolls': roll_results,
|
2024-06-22 10:29:05 +00:00
|
|
|
'result': sum(roll_results)
|
2024-06-21 21:49:11 +00:00
|
|
|
}
|
2024-06-22 10:29:05 +00:00
|
|
|
if discarded:
|
|
|
|
result['discarded'] = discarded
|
2024-06-21 21:49:11 +00:00
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2024-06-23 17:30:13 +00:00
|
|
|
def roll_ability_scores(chartype, attribute=None):
|
|
|
|
if attribute is None or attribute == "all":
|
|
|
|
ability_list = ['p-strength', 'm-strength', 'constitution', 'dexterity', 'charisma', 'intelligence']
|
2024-06-22 13:22:39 +00:00
|
|
|
else:
|
2024-06-23 17:30:13 +00:00
|
|
|
ability_list = [attribute]
|
2024-06-22 13:22:39 +00:00
|
|
|
|
|
|
|
ability_scores = {}
|
|
|
|
|
2024-06-23 17:30:13 +00:00
|
|
|
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"]
|
2024-06-22 13:22:39 +00:00
|
|
|
|
|
|
|
return ability_scores
|
|
|
|
|
2024-06-22 18:32:17 +00:00
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
def roll_hp(chartype, conscore):
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
return roll_dices(conscore, geometry, False).get('result')
|
|
|
|
|
2024-06-22 18:32:17 +00:00
|
|
|
|
2024-06-23 17:30:13 +00:00
|
|
|
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"]
|
2024-06-23 17:37:58 +00:00
|
|
|
phys_cnt = split_number(cybermods_count)[1] # This ends up being the higher of the two numbers
|
|
|
|
ment_cnt = split_number(cybermods_count)[0]
|
2024-06-23 17:34:37 +00:00
|
|
|
cybermods_table['count'] = {'mental': ment_cnt, 'physical': phys_cnt}
|
2024-06-23 17:30:13 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-06-22 18:32:17 +00:00
|
|
|
def roll_mutations(conscore, intscore):
|
|
|
|
"""
|
|
|
|
:param conscore: modifier for physical mutation determination
|
|
|
|
:param intscore: modifier for mental mutation determination
|
|
|
|
:return: table of mutations (in json form)
|
|
|
|
"""
|
|
|
|
mutations_table = {}
|
|
|
|
|
2024-06-22 21:29:19 +00:00
|
|
|
mental_mutations_cnt = roll_dices(1, 4, False).get('result')
|
2024-06-22 18:32:17 +00:00
|
|
|
physical_mutations_cnt = roll_dices(1, 4, False).get('result')
|
|
|
|
mutations_table['count'] = {'mental': mental_mutations_cnt, 'physical': physical_mutations_cnt}
|
|
|
|
|
|
|
|
mental_mutations_scores = []
|
|
|
|
physical_mutations_scores = []
|
|
|
|
|
|
|
|
for _ in range(mental_mutations_cnt):
|
|
|
|
mscore = roll_dices(1, 100, False).get('result') + intscore
|
|
|
|
if mscore in mental_mutations_scores:
|
2024-06-23 17:30:13 +00:00
|
|
|
mscore = roll_dices(1, 100, False).get('result') + intscore
|
2024-06-22 18:32:17 +00:00
|
|
|
if mscore > 100:
|
|
|
|
mscore = 100
|
|
|
|
mental_mutations_scores.append(mscore)
|
|
|
|
|
|
|
|
for _ in range(physical_mutations_cnt):
|
|
|
|
pscore = roll_dices(1, 100, False).get('result') + conscore
|
|
|
|
if pscore in physical_mutations_scores:
|
2024-06-23 17:30:13 +00:00
|
|
|
pscore = roll_dices(1, 100, False).get('result') + conscore
|
2024-06-22 18:32:17 +00:00
|
|
|
if pscore > 100:
|
|
|
|
pscore = 100
|
|
|
|
physical_mutations_scores.append(pscore)
|
|
|
|
|
|
|
|
mut = Mutations()
|
|
|
|
mmut = []
|
|
|
|
pmut = []
|
|
|
|
|
|
|
|
for score in mental_mutations_scores:
|
|
|
|
mutation = mut.get_mental_mutation(score)
|
|
|
|
mmut.append(mutation)
|
|
|
|
|
|
|
|
mutations_table['mental'] = mmut
|
|
|
|
|
|
|
|
for score in physical_mutations_scores:
|
|
|
|
mutation = mut.get_physical_mutation(score)
|
|
|
|
pmut.append(mutation)
|
|
|
|
|
|
|
|
mutations_table['physical'] = pmut
|
|
|
|
|
|
|
|
return mutations_table
|
|
|
|
|
|
|
|
|
2024-06-23 23:47:38 +00:00
|
|
|
def roll_mutant_animal():
|
|
|
|
creature = dict()
|
|
|
|
|
|
|
|
animal_stock = ['badger', 'racoon', 'hound', 'wolf', 'big-cat', 'fox',
|
|
|
|
'spider', 'lizard', 'ant', 'hornet', 'hawk', 'ostrich', 'emu', 'crocodile',
|
|
|
|
'snake', 'rabbit', 'rat', 'bear', 'elephant', 'platypus', 'bull', 'horse', 'goat', 'bat',
|
|
|
|
'silverfish', 'cockroach', 'turtle', 'gibbon', 'penguin', 'orangutan', 'chimpanzee',
|
|
|
|
'housefly', 'lobster', 'crab', 'prawn', 'pig', 'chicken', 'duck', 'parrot', 'mouse',
|
|
|
|
'heron', 'weasel', 'squirrel', 'pigeon', 'crow', 'house-cat', 'shark', 'dolphin', 'narwhal',
|
|
|
|
'buffalo']
|
|
|
|
|
|
|
|
posture = ['upright', 'prone', 'mixed']
|
|
|
|
|
|
|
|
creature['stock'] = random.choice(animal_stock)
|
|
|
|
creature['posture'] = random.choice(posture)
|
|
|
|
|
|
|
|
if creature['posture'] == 'upright' or creature['posture'] == 'mixed':
|
|
|
|
creature['arms'] = random.choice(list(range(2, 4, 2))) # if you're upright 4 is the limit
|
|
|
|
creature['legs'] = random.choice(list(range(2, 6, 2))) # same with legs
|
|
|
|
|
|
|
|
if creature['posture'] == 'prone':
|
|
|
|
creature['legs'] = random.choice(list(range(4, 8, 2))) # if you're prone, you only get legs
|
|
|
|
|
|
|
|
|
|
|
|
return creature
|
|
|
|
|
|
|
|
|
2024-06-23 17:30:13 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-06-21 21:49:11 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
app.run()
|