Added skills and Partial support for feats.. Breaking change.. I really
need to start doing pull requests and branches..
Showing
5 changed files
with
201 additions
and
6 deletions
1 | import sys | ||
2 | |||
3 | if 'WiPy' in sys.platform: | ||
4 | from ucrypto import getrandbits | ||
5 | import network | ||
6 | wlan = network.WLAN(mode=network.WLAN.STA) | ||
7 | wlan.connect('Volley', auth=(network.WLAN.WPA2, '6198472223')) | ||
8 | |||
1 | import weemud | 9 | import weemud |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | {"name": "test", "password": "6c76899eb15393064b4f4db94805e5862232920b", "room": "town/tavern", "abilities": {"str": 10, "dex": 23, "con": 10, "int": 12, "wis": 10, "cha": 8}, "equipment": {"finger": [null, null], "hand": [null, null], "arm": [null, null], "leg": [null, null], "foot": [null, null], "head": null, "neck": null, "back": null, "body": null, "waist": null}, "inventory": {"bag": [{"expires": 0, "inventory": {"shirt": [{"expires": 0}]}}]}, "prompt": "hp %hp mp %mp> ", "aliases": {}, "hp": 100, "mp": 10, "maxhp": 100, "maxmp": 10, "maxsta": 10, "sta": 10, "aa": "1d2", "mpr": 0.25, "star": 0.4, "weapon": null, "sp": {}, "at": {"kick": {"cost": 5, "dmg": "2d4", "desc": "You unleash a powerful kick"}}, "createstep": 7, "color_enabled": true, "over_13": true, "race": "Android", "theme": "Ace Pilot", "class": "Envoy", "abilitypoints": 0, "baseattack": 0, "fort": 0, "ref": 2, "will": 2} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | {"name": "test", "password": "6c76899eb15393064b4f4db94805e5862232920b", "room": "town/tavern", "abilities": {"str": 10, "dex": 11, "con": 10, "int": 10, "wis": 10, "cha": 10}, "equipment": {"finger": [null, null], "hand": [null, null], "arm": [null, null], "leg": [null, null], "foot": [null, null], "head": null, "neck": null, "back": null, "body": null, "waist": null}, "inventory": {"bag": [{"expires": 0, "inventory": {"shirt": [{"expires": 0}]}}]}, "skills": {}, "prompt": "hp %hp mp %mp> ", "aliases": {}, "level": 1, "hp": 10, "mp": 10, "maxhp": 10, "maxmp": 10, "maxsta": 10, "sta": 10, "aa": "1d2", "mpr": 0.25, "star": 0.4, "weapon": null, "sp": 7, "at": {"kick": {"cost": 5, "dmg": "2d4", "desc": "You unleash a powerful kick"}}, "createstep": 7, "color_enabled": true, "over_13": true, "race": "Human", "theme": "Ace Pilot", "class": "Envoy", "abilitypoints": 12, "baseattack": 0, "fort": 0, "ref": 2, "will": 2, "maxsp": 7, "maxrp": 3, "rp": 3, "skillpoints": 9} | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -3,6 +3,8 @@ import sys | ... | @@ -3,6 +3,8 @@ import sys |
3 | 3 | ||
4 | if 'esp' in sys.platform: | 4 | if 'esp' in sys.platform: |
5 | from urandom import getrandbits | 5 | from urandom import getrandbits |
6 | elif 'WiPy' in sys.platform: | ||
7 | from ucrypto import getrandbits | ||
6 | else: | 8 | else: |
7 | from random import getrandbits | 9 | from random import getrandbits |
8 | 10 | ||
... | @@ -15,7 +17,7 @@ codes = {'resetall': 0, 'bold': 1, 'underline': 4, | ... | @@ -15,7 +17,7 @@ codes = {'resetall': 0, 'bold': 1, 'underline': 4, |
15 | 17 | ||
16 | 18 | ||
17 | def password_hash(name, password): | 19 | def password_hash(name, password): |
18 | if 'esp' in sys.platform: | 20 | if sys.platform in ['esp', 'WiPy']: |
19 | import uhashlib | 21 | import uhashlib |
20 | return ''.join(['%.2x' % i for i in uhashlib.sha1(password + 'weemud' + name).digest()]) | 22 | return ''.join(['%.2x' % i for i in uhashlib.sha1(password + 'weemud' + name).digest()]) |
21 | else: | 23 | else: | ... | ... |
... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
3 | """ | 3 | """ |
4 | MudServer author: Mark Frimston - mfrimston@gmail.com | 4 | MudServer author: Mark Frimston - mfrimston@gmail.com |
5 | 5 | ||
6 | Micropython port and expansion author: Barry Ruffner - barryruffner@gmail.com | 6 | Micropython port and extensions for more protocol author: Barry Ruffner - barryruffner@gmail.com |
7 | """ | 7 | """ |
8 | 8 | ||
9 | from time import sleep | 9 | from time import sleep |
... | @@ -68,6 +68,134 @@ races = { | ... | @@ -68,6 +68,134 @@ races = { |
68 | } | 68 | } |
69 | } | 69 | } |
70 | 70 | ||
71 | feats = { | ||
72 | """ | ||
73 | Adaptive Fighting* | ||
74 | Three or more combat feats Once per day as a move action, gain the benefit of a combat feat you don’t have | ||
75 | Amplified Glitch* | ||
76 | Computers 3 ranks, Intimidate 3 ranks Disrupt devices, causing targets to become shaken for 1 round or more Antagonize Diplomacy 5 ranks, Intimidate 5 ranks Anger a foe, causing it to become off-target and take a −2 penalty to skill checks for 1 round or more | ||
77 | Barricade* | ||
78 | Engineering 1 rank Create your own fragile cover | ||
79 | Basic Melee Weapon Proficiency* | ||
80 | — No penalty to attacks with basic melee weapons | ||
81 | Advanced Melee Weapon Proficiency* | ||
82 | Basic Melee Weapon Proficiency No penalty to attacks with advanced melee weapons | ||
83 | Special Weapon Proficiency* | ||
84 | Basic Melee Weapon Proficiency or Small Arm Proficiency No penalty to attacks with one special weapon | ||
85 | Blind-Fight* | ||
86 | — Reroll miss chances from concealment | ||
87 | Bodyguard* | ||
88 | — Add a +2 bonus to an adjacent ally’s AC as a reaction | ||
89 | In Harm’s Way* | ||
90 | Bodyguard Take the damage of a successful attack against an adjacent ally | ||
91 | Cleave* | ||
92 | Str 13, base attack bonus +1 Make an additional melee attack if the first one hits | ||
93 | Great Cleave* | ||
94 | Str 13, Cleave, base attack bonus +4 Make an additional melee attack after each melee attack that hits Climbing Master Athletics 5 ranks Gain a climb speed equal to your base speed | ||
95 | Combat Casting* | ||
96 | Ability to cast 2nd-level spells +2 bonus to AC and saves against attacks of opportunity when casting spells Connection Inkling Wis 15, character level 5th, no mystic levels Gain the ability to cast minor mystic spells | ||
97 | Coordinated Shot* | ||
98 | Base attack bonus +1 Allies gain a +1 bonus to ranged attacks against foes you threaten | ||
99 | Deadly Aim* | ||
100 | Base attack bonus +1 Take a −2 penalty to weapon attacks to deal extra damage | ||
101 | Deflect Projectiles* | ||
102 | Base attack bonus +8 Spend 1 Resolve Point to attempt to avoid a ranged attack | ||
103 | Reflect Projectiles* | ||
104 | Deflect Projectiles, base attack bonus +16 Spend 1 Resolve Point to attempt to redirect a ranged attack Diehard — You can spend Resolve Points to stabilize and to stay in the fight in the same round Dive for Cover* | ||
105 | Base Reflex save bonus +2 Fall prone in an adjacent square to roll a Reflex save twice Diversion — Use Bluff to create a distraction so that your allies can hide | ||
106 | Drag Down* | ||
107 | — When you are tripped, you can attempt to trip an adjacent foe Enhanced Resistance Base attack bonus +4 Gain damage reduction or energy resistance Extra Resolve Character level 5th Gain 2 additional Resolve Points | ||
108 | Far Shot* | ||
109 | Base attack bonus +1 Reduce penalty due to range increments Fast Talk Bluff 5 ranks Baffle a potential foe, causing it to be surprised when combat begins | ||
110 | Fleet* | ||
111 | — Increase your base speed | ||
112 | Fusillade* | ||
113 | Base attack bonus +1, 4 or more arms Make an automatic-mode attack with multiple small arms Great Fortitude — +2 bonus to Fortitude saves Improved Great Fortitude Great Fortitude, character level 5th Spend 1 Resolve Point to reroll a Fortitude save | ||
114 | Grenade Proficiency* | ||
115 | — No penalty to attacks made with grenades Harm Undead Healing channel connection power, mystic level 1st Expend a spell slot for healing channel to also damage undead | ||
116 | Improved Combat Maneuver* | ||
117 | Base attack bonus +1 +4 bonus to perform one combat maneuver | ||
118 | Pull the Pin* | ||
119 | Improved Combat Maneuver (disarm) Perform a disarm to activate a foe’s grenade | ||
120 | Improved Critical* | ||
121 | Base attack bonus +8 The DC to resist the critical effects of your critical hits increases by 2 | ||
122 | Improved Feint* | ||
123 | — Use Bluff to feint as a move action | ||
124 | Greater Feint* | ||
125 | Improved Feint, base attack bonus +6 Foes you feint against are flat-footed for 1 round | ||
126 | Improved Initiative* | ||
127 | — +4 bonus to initiative checks | ||
128 | Improved Unarmed Strike* | ||
129 | — Deal more damage and threaten squares with unarmed strikes Iron Will — +2 bonus to Will saves Improved Iron Will Iron Will, character level 5th Spend 1 Resolve Point to reroll a Will save Jet Dash — Move faster when running, double height and distance when jumping | ||
130 | Kip Up* | ||
131 | Acrobatics 1 rank Stand from prone as a swift action | ||
132 | Light Armor Proficiency* | ||
133 | — No penalty to attack rolls while wearing light armor | ||
134 | Heavy Armor Proficiency* | ||
135 | Str 13, Light Armor Proficiency No penalty to attack rolls while wearing heavy armor | ||
136 | Powered Armor Proficiency* | ||
137 | Str 13, Light Armor Proficiency, Heavy Armor Proficiency, base attack bonus +5 No penalty to attack rolls while wearing powered armor Lightning Reflexes — +2 bonus to Reflex saves Improved Lightning Reflexes Lightning Reflexes, character level 5th Spend 1 Resolve Point to reroll a | ||
138 | Reflex save Lunge* | ||
139 | Base attack bonus +6 Increase reach of melee attacks by 5 feet until the end of your turn Master Crafter Computers, Engineering, Life Science, Mysticism, Physical Science, or Profession 5 ranks Craft items in half the normal time Medical Expert Life Science 1 rank, Medicine 1 rank, Physical Science 1 rank Treat deadly wounds more quickly, and provide long-term care without a medical lab Minor Psychic Power Cha 11 Cast a 0-level spell as a spell-like ability 3/day Psychic Power Cha 13, Minor Psychic Power, character level 4th Cast a 1st-level spell as a spell-like ability 1/day Major Psychic Power Cha 15, Minor Psychic Power, Psychic Power, character level 7th Cast a 2nd-level spell as a spell-like ability 1/day | ||
140 | Mobility* | ||
141 | Dex 13 +4 bonus to AC against attacks of opportunity from movement Agile Casting Key ability score 15, Dex 15, Mobility, caster level 4th Cast a spell at any point during movement Shot on the | ||
142 | Run* | ||
143 | Dex 15, Mobility, base attack bonus +4 Make a ranged attack at any point during movement | ||
144 | Parting Shot* | ||
145 | Dex 15, Mobility, Shot on the Run, base attack bonus +6 Make a single ranged attack when withdrawing Sidestep* Dex 15, Mobility or trick attack Take guarded step as a reaction when a foe misses you with melee attack | ||
146 | Improved Sidestep* Dex 17, Mobility or trick attack class feature, Sidestep Reduce penalties from Sidestep Spring Attack* | ||
147 | Dex 15, Mobility, base attack bonus +4 Move before and after a melee attack | ||
148 | Multi-Weapon Fighting* | ||
149 | — Reduce the penalty for full attacks when using multiple small arms or operative melee weapons | ||
150 | Mystic Strike* | ||
151 | Ability to cast spells Melee and ranged attacks count as magic | ||
152 | Nimble Moves* | ||
153 | Dex 15 Ignore 20 feet of difficult terrain when you move | ||
154 | Opening Volley* | ||
155 | — +2 bonus to a melee attack against a target you damaged with a ranged attack | ||
156 | Penetrating Attack* | ||
157 | Base attack bonus +12 Reduce enemy's DR and energy resistance against your weapons by 5 Penetrating Spell Ability to cast 4th-level spells Reduce enemy's DR and energy resistance against your spells by 5 | ||
158 | Quick Draw* | ||
159 | Base attack bonus +1 Draw a weapon as a swift action Skill Focus — +3 insight bonus to one skill Skill Synergy — Gain two new class skills or a +2 insight bonus to those skills Sky Jockey Piloting 5 ranks Make jetpacks, vehicles, and starships go faster | ||
160 | Slippery Shooter* | ||
161 | Dex 15, base attack bonus +6 +3 bonus to AC against attacks of opportunity when making ranged attacks | ||
162 | Small Arm Proficiency* | ||
163 | — No penalty to attacks with small arms | ||
164 | Longarm Proficiency* | ||
165 | Small Arm Proficiency No penalty to attacks with longarms | ||
166 | Heavy Weapon Proficiency* | ||
167 | Str 13, Longarm Proficiency, Small Arm Proficiency No penalty to attacks with heavy weapons | ||
168 | Special Weapon Proficiency* | ||
169 | Basic Melee Weapon Proficiency or Small Arm Proficiency No penalty to attacks with one special weapon | ||
170 | Sniper Weapon Proficiency* | ||
171 | — No penalty to attacks with sniper weapons Spell Focus Ability to cast spells, character level 3rd DCs of spells you cast increase Spell Penetration — +2 bonus to caster level checks to overcome SR Greater Spell Penetration Spell Penetration Additional +2 bonus to caster level checks to overcome SR Spellbane Unable to cast spells or use spell-like abilities +2 insight bonus to saving throws against spells and spell-like abilities | ||
172 | Spry Cover* | ||
173 | Base attack bonus +1 Covering fire grants a +4 bonus to an ally’s Acrobatics check to tumble | ||
174 | Stand Still* | ||
175 | — Make an attack of opportunity to stop a foe’s movement | ||
176 | Improved Stand Still* | ||
177 | Stand Still +4 bonus to melee attacks with Stand Still | ||
178 | Step Up* | ||
179 | Base attack bonus +1 Take a guarded step as a reaction to an adjacent foe moving Step Up | ||
180 | and Strike* | ||
181 | Dex 13, Step Up, base attack bonus +6 Make an attack of opportunity as part of Step Up | ||
182 | Suppressive Fire* | ||
183 | Base attack bonus +1, proficiency with heavy weapons Provide covering fire or harrying fire in an area | ||
184 | Strike Back* | ||
185 | Base attack bonus +1 Ready an action to make a melee attack against a foe with reach Swimming Master Athletics 5 ranks Gain a swim speed equal to your base speed Technomantic Dabbler Int 15, character level 5th, no levels in technomancer Gain the ability to cast minor technomancer spells Toughness — +1 Stamina Point per character level and other bonuses | ||
186 | Unfriendly Fire* | ||
187 | Bluff 5 ranks Trick an attacker into shooting at another enemy adjacent to you Veiled Threat Cha 15, Intimidate 1 rank Intimidated foe doesn’t become hostile | ||
188 | Weapon Focus* | ||
189 | Proficiency with selected weapon type +1 bonus to attack rolls with selected weapon type | ||
190 | Versatile Focus* | ||
191 | Weapon Focus +1 bonus to attack rolls with all weapon types you are proficient with | ||
192 | Weapon Specialization* | ||
193 | Character level 3rd, proficiency with selected weapon type Deal extra damage with selected weapon type | ||
194 | Versatile Specialization* | ||
195 | Weapon Specialization, character level 3rd Deal extra damage with all weapon types you are proficient with | ||
196 | """ | ||
197 | } | ||
198 | |||
71 | themes = { | 199 | themes = { |
72 | "Ace Pilot": { | 200 | "Ace Pilot": { |
73 | "mods": {"dex": 1} | 201 | "mods": {"dex": 1} |
... | @@ -272,8 +400,8 @@ def isalnum(c): | ... | @@ -272,8 +400,8 @@ def isalnum(c): |
272 | def show_list(id, title, singular_title, subject_list, instructions='Please choose a {} from the list by number: '): | 400 | def show_list(id, title, singular_title, subject_list, instructions='Please choose a {} from the list by number: '): |
273 | mud.send_message(id, " %green+------------={}=------------+".format(title), nowrap=True) | 401 | mud.send_message(id, " %green+------------={}=------------+".format(title), nowrap=True) |
274 | for idx, subject in enumerate(subject_list): | 402 | for idx, subject in enumerate(subject_list): |
275 | mud.send_message(id, " %%green|%%reset%%bold%%white%s%%reset%%green|" % ('{}. {:<28}'.format(idx, subject),), nowrap=True) | 403 | mud.send_message(id, " %%green|%%reset%%bold%%white%s%%reset%%green|" % ('{:<2}. {:<28}'.format(idx, subject),), nowrap=True) |
276 | mud.send_message(id, " %green+-------------------------------+", nowrap=True) | 404 | mud.send_message(id, " %green+--------------------------------+", nowrap=True) |
277 | 405 | ||
278 | mud.send_message(id, ' (Type %boldhelp [{}]%reset for details)\r\n'.format(singular_title)) | 406 | mud.send_message(id, ' (Type %boldhelp [{}]%reset for details)\r\n'.format(singular_title)) |
279 | mud.send_message(id, instructions.format(singular_title)) | 407 | mud.send_message(id, instructions.format(singular_title)) |
... | @@ -286,8 +414,20 @@ def get_base_abilities(id): | ... | @@ -286,8 +414,20 @@ def get_base_abilities(id): |
286 | base[name] += value | 414 | base[name] += value |
287 | return base | 415 | return base |
288 | 416 | ||
417 | def show_skills(id): | ||
418 | mud.send_message(id, "%greenEnter the number of the skill you wish to modify, 1 point will be added to that skill rank. You may not exceed the available skill points.") | ||
419 | mud.send_message(id, '%greenYou can reset to the base values by typing %boldreset') | ||
420 | mud.send_message(id, " %green+--------=Skill Ranks=-------+", nowrap=True) | ||
421 | for idx, skill in enumerate(classes[players[id]["class"]]["skills"]): | ||
422 | skill_rank = players[id]["skills"].get(skill, 0) | ||
423 | mud.send_message(id, " %%green|%%reset%%bold%%white%s%%reset%%green|" % ('{:<2}. {:<19} {:^4}'.format(idx, skill, skill_rank),), nowrap=True) | ||
424 | mud.send_message(id, " %green+- Points: %bold{:<2}%reset%green ---------------+".format(players[id]["skillpoints"]), nowrap=True) | ||
425 | |||
426 | mud.send_message(id, ' (Type %boldhelp [skill]%reset for details)\r\n') | ||
427 | mud.send_message(id, '%greenWhen finished type %bolddone') | ||
428 | |||
289 | def show_abilities(id): | 429 | def show_abilities(id): |
290 | mud.send_message(id, "%greenEnter the number of the ability you wish to modify 1 point will be added to that ability. You may not exceed the available ability points.") | 430 | mud.send_message(id, "%greenEnter the number of the ability you wish to modify, 1 point will be added to that ability. You may not exceed the available ability points.") |
291 | mud.send_message(id, '%greenYou can reset to the base values by typing %boldreset') | 431 | mud.send_message(id, '%greenYou can reset to the base values by typing %boldreset') |
292 | mud.send_message(id, " %green+-----=Abilities=-----+", nowrap=True) | 432 | mud.send_message(id, " %green+-----=Abilities=-----+", nowrap=True) |
293 | for idx, ability in enumerate(players[id]["abilities"]): | 433 | for idx, ability in enumerate(players[id]["abilities"]): |
... | @@ -396,8 +536,12 @@ def handle_character_create(id, command, params): | ... | @@ -396,8 +536,12 @@ def handle_character_create(id, command, params): |
396 | if players[id]["maxrp"] < 1: | 536 | if players[id]["maxrp"] < 1: |
397 | players[id]["maxrp"] = 1 | 537 | players[id]["maxrp"] = 1 |
398 | players[id]["rp"] = players[id]["maxrp"] | 538 | players[id]["rp"] = players[id]["maxrp"] |
539 | players[id]["skillpoints"] = get_ability_mod(id, 'int') + pclass["skillperlevel"] | ||
540 | |||
399 | players[id]["createstep"] = 7 | 541 | players[id]["createstep"] = 7 |
542 | show_skills(id) | ||
400 | elif not command.isdigit() or int(command) + 1 > len(players[id]["abilities"]) or int(command) < 0: | 543 | elif not command.isdigit() or int(command) + 1 > len(players[id]["abilities"]) or int(command) < 0: |
544 | mud.send_message(id, '%cyanInvalid number') | ||
401 | show_abilities(id) | 545 | show_abilities(id) |
402 | else: | 546 | else: |
403 | ability_name = list(players[id]["abilities"])[int(command)] | 547 | ability_name = list(players[id]["abilities"])[int(command)] |
... | @@ -407,6 +551,46 @@ def handle_character_create(id, command, params): | ... | @@ -407,6 +551,46 @@ def handle_character_create(id, command, params): |
407 | players[id]["abilities"][ability_name] += 1 | 551 | players[id]["abilities"][ability_name] += 1 |
408 | players[id]["abilitypoints"] -= 1 | 552 | players[id]["abilitypoints"] -= 1 |
409 | show_abilities(id) | 553 | show_abilities(id) |
554 | elif players[id]['createstep'] == 7: | ||
555 | if command.lower().startswith("help"): | ||
556 | cmd_handler.parse(id, command, params.lower().strip(), mud, players) | ||
557 | elif command.lower().startswith("reset"): | ||
558 | pclass = classes[players[id]["class"]] | ||
559 | players[id]["skills"] = { } | ||
560 | players[id]["skillpoints"] = get_ability_mod(id, 'int') + pclass["skillperlevel"] | ||
561 | show_skills(id) | ||
562 | elif command.lower().startswith("done"): | ||
563 | if players[id]["skillpoints"] > 0: | ||
564 | mud.send_message(id, '%cyanPlease spend all available skill points.') | ||
565 | show_skills(id) | ||
566 | else: | ||
567 | players[id]["createstep"] = 8 | ||
568 | show_list(id, "Feats", "feat", feats) | ||
569 | elif not command.isdigit() or int(command) + 1 > len(themes) or int(command) < 0: | ||
570 | show_skills(id) | ||
571 | else: | ||
572 | skill_name = classes[players[id]["class"]]["skills"][int(command)] | ||
573 | if players[id]["skillpoints"] > 0: | ||
574 | if players[id]["skills"].get(skill_name): | ||
575 | players[id]["skills"][skill_name] += 1 | ||
576 | else: | ||
577 | players[id]["skills"][skill_name] = 1 | ||
578 | players[id]["skillpoints"] -= 1 | ||
579 | show_skills(id) | ||
580 | elif players[id]['createstep'] == 8: | ||
581 | if command.lower().startswith("help"): | ||
582 | cmd_handler.parse(id, command, params.lower().strip(), mud, players) | ||
583 | elif not command.isdigit() or int(command) + 1 > len(themes) or int(command) < 0: | ||
584 | show_list(id, "Feats", "feat", feats) | ||
585 | else: | ||
586 | # skill_name = classes[players[id]["class"]]["skills"][int(command)] | ||
587 | # if players[id]["skillpoints"] > 0: | ||
588 | # if players[id]["skills"].get(skill_name): | ||
589 | # players[id]["skills"][skill_name] += 1 | ||
590 | # else: | ||
591 | # players[id]["skills"][skill_name] = 1 | ||
592 | # players[id]["skillpoints"] -= 1 | ||
593 | show_list(id, "Feats", "feat", feats) | ||
410 | save_object_to_file(players[id], "players/{}.json".format(players[id]["name"])) | 594 | save_object_to_file(players[id], "players/{}.json".format(players[id]["name"])) |
411 | 595 | ||
412 | # main game loop. We loop forever (i.e. until the program is terminated) | 596 | # main game loop. We loop forever (i.e. until the program is terminated) | ... | ... |
-
Please register or sign in to post a comment