diff --git a/negotiation.py b/negotiation.py index d974833..22cc1ca 100755 --- a/negotiation.py +++ b/negotiation.py @@ -108,14 +108,36 @@ def handle_join_negotiation(json): json['room'], len(participants))) flask_socketio.emit('participants changed', {'participants': participants}, room = json['room']) - return True + # Update the user's negotation state with what is stored on the server + nego = get_negotiation(json['room']) + return nego.to_dict() def get_room_participants(room): + if '/' not in socketio.server.manager.get_namespaces(): + return [] sessions = [s for s in socketio.server.manager.get_participants('/', room)] participants = [p for s,p in session_map.items() if s in sessions] app.logger.debug(participants) return participants +@socketio.on('update negotiation') +@pony.orm.db_session +def handle_update_negotiation(json): + r = flask.Response() + app.logger.info('Received update request: {}'.format(json)) + uid, user = get_uid(r) + nego = get_negotiation(json['room']) + app.logger.info('Room owner is {}'.format(nego.owner.uid)) + if nego.owner.uid != uid: + # Refuse the update from non-owners + app.logger.warning('Refusing update of {} from non-owner {}'.format(json['room'], uid)) + return False + del json['room'] + nego.set(**json) + pony.orm.commit() + flask_socketio.emit('negotiation updated', {**json}, room = nego.name) + return True + @socketio.on('leave negotiation') def handle_leave_disconnect(json): r = flask.Response() @@ -186,9 +208,64 @@ if __name__ == '__main__': uid = pony.orm.Required(str) display_name = pony.orm.Optional(str) negotiations = pony.orm.Set('Negotiation') + + # Negotiations have a state. Maybe storing a state machine object (pickle?) + # would be an idea. But maybe not. + # + # Let's start with understanding negotiations + # 1. Prep Work: Before a negotiation begins, each take may do Prep Work + # 2. The takers decide who their Lead Negotiator will be. Once set, this + # cannot be changed. + # 3. The Lead Negotiation makes a First Impression + # This is a leadership check which has four effects: + # 1. The number of rounds of negotiation: max(floor(black+ld/2), 5) + # 2. The starting position (crit success +1 sway, crit failure: -1 sway) + # 3. Whether or not the negotiation length is known to takers. T/F based + # simple succes. + # 4. Who leads the negotiations (starting round): takers on success, Market otherwise. + # 4. Negotiation Round(s) + # 1. Lead negotiator plays a negotiation tactic + # 2. Other negotiator plays a negotiation tactic + # 3. Check negotiation end condition + # 4. If not ended, a non-negotiator taker may play scam + # 5. Wrap-up + # 1. Taker negotiator rolls Leadership. Success moves up to meet Market, + # failure moves market down to meet takers. + # 2a. Takers may accept the deal. + # 2b. Takers may back out. Next negotiation starts on lowest tracker, has + # no prep work, scams, or will. + # 3. Competition undercuts. If able, the competition will undercut by one + # spot on the way tracker. _Any_ taker may make a CHA check to stop this, + # but on failure the takers must lower theire price to match the competition. + # class Negotiation(db.Entity): name = pony.orm.PrimaryKey(str) owner = pony.orm.Required(User) + client_name = pony.orm.Optional(str) + negotiatior_name = pony.orm.Optional(str) + taker_crew_name = pony.orm.Optional(str) + # Manual negotation + manual_negotiation = pony.orm.Optional(bool) + # Pre-negotiation details + takers = pony.orm.Optional(int) + lead_negotiator = pony.orm.Optional(str) + is_first_negotiation = pony.orm.Optional(bool) + first_impression_black = pony.orm.Optional(int) + # One of critfail, fail, success, critsuccess + first_impression_state = pony.orm.Optional(str) + negotiation_round = pony.orm.Optional(int) + # One of leadnego, secondnego, scam + negotiation_phase = pony.orm.Optional(str) + taker_sway = pony.orm.Optional(int) + market_sway = pony.orm.Optional(int) + # If negotiation_round = (calculed length) && negotiation_phase = scam then + # the negotiation is no into the wrap up section + # One of critfail, fail, success, critsuccess + wrapup_state = pony.orm.Optional(int) + job_accepted = pony.orm.Optional(bool) + may_be_undercut = pony.orm.Optional(bool) + undercut = pony.orm.Optional(bool) + db.bind(**app.config['PONY']) db.generate_mapping(create_tables=True) Pony(app) diff --git a/static/css/style.css b/static/css/style.css index 8e78d54..1fa3d78 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -964,6 +964,53 @@ img { margin: 0; float: right; } + +.negotiation-wrapper { + display: flex; +} + +.negotiation-sidebar { + max-width: 30%; +} + +.negotiation-panel { + min-width: 70%; +} + +/* maybe this should be grids */ +.swaytracker .slot { + min-height: 50px; + min-width: 150px; + max-width: 13%; + border: 1px solid; +} + +.swaytracker th { + color: #600000; + background: white; + border-color: black; +} + +.swaytracker .market-position .active { + height: 50px; + background-image: url("/static/images/active-market.png"); + background-repeat: no-repeat; + background-position: center; +} +.swaytracker .taker-position .active { + height: 50px; + background-image: url("/static/images/active-taker.png"); + background-repeat: no-repeat; + background-position: center; +} +.swaytracker .slot span { + font-weight: bold; +} + +.swaytracker .slot p { + margin: 0.5em; +} + #wrapper { position: relative; padding-right: 0; diff --git a/static/images/active-market.png b/static/images/active-market.png new file mode 100644 index 0000000..c05e066 Binary files /dev/null and b/static/images/active-market.png differ diff --git a/static/images/active-taker.png b/static/images/active-taker.png new file mode 100644 index 0000000..4463ec1 Binary files /dev/null and b/static/images/active-taker.png differ diff --git a/static/images/drag.png b/static/images/drag.png new file mode 100644 index 0000000..cbf032c Binary files /dev/null and b/static/images/drag.png differ diff --git a/templates/negotiation.html b/templates/negotiation.html index ac9f171..aff7f08 100644 --- a/templates/negotiation.html +++ b/templates/negotiation.html @@ -27,9 +27,12 @@ // const negotiation = new Negotiation("{{negotiation.name}}") var socket = io(); var room = "{{negotiation.name}}"; + socket.on('connect', function() { console.log("Trying to join room " + room); - socket.emit('join negotiation', {"room": room}); + socket.emit('join negotiation', {"room": room}, function(e) { + update_from_data(e); + }); }); window.addEventListener('beforeunload', function(e) { console.log("Trying to leave room " + room); @@ -60,5 +63,65 @@ } } }); + socket.on('negotiation updated', function(e) { + update_from_data(e); + }); + + function update_from_data(e) { + console.log(e); + if ('taker_sway' in e) { + // Update taker way + var current_taker_sway = $('.swaytracker .taker-position .active').attr('id'); + change_swayslot(current_taker_sway, 't' + e.taker_sway); + } + if ('market_sway' in e) { + var current_market_sway = $('.swaytracker .market-position .active').attr('id'); + change_swayslot(current_market_sway, 'm' + e.market_sway); + } + } + function swayslot_on_dragstart(event) { + var ev = event.originalEvent; + ev.dataTransfer.setData("text/plain", ev.target.id); + let img = new Image(); + img.src = '/static/images/drag.png'; + ev.dataTransfer.setDragImage(img, 0, 0); + ev.dataTransfer.dropEffect = "move"; + } + function swayslot_on_dragover(event) { + event.preventDefault(); + event.originalEvent.dataTransfer.dropEffect = "move"; + } + function swayslot_on_drop(event) { + event.preventDefault(); + var source = event.originalEvent.dataTransfer.getData('text/plain'); + // Check to see if this is in the same track (id starts with same letter) + var this_id = $(this).attr('id'); + if (this_id[0] == source[0] && this_id != source) { + var key = (this_id[0] == 't' ? 'taker_sway' : 'market_sway'); + var value = this_id[1]; + var data = { + "room": room, + }; + data[key] = value; + socket.emit('update negotiation', data, function (confirmation) { + if (confirmation) { + change_swayslot(source, this_id); + } + }); + } + } + function change_swayslot(from, to) { + $("#" + from).removeClass('active').unbind('dragstart').attr('draggable', false); + $('#' + to).addClass('active').on('dragstart', swayslot_on_dragstart).attr('draggable', true); + } + // Drag/drog for the swaytracker + window.addEventListener('DOMContentLoaded', () => { + $('.swaytracker .active').on('dragstart', swayslot_on_dragstart); + $('.swaytracker .active').attr('draggable', true); + $('.swaytracker .market-position .slot').on('dragover', swayslot_on_dragover); + $('.swaytracker .taker-position .slot').on('dragover', swayslot_on_dragover); + $('.swaytracker .market-position .slot').on('drop', swayslot_on_drop); + $('.swaytracker .taker-position .slot').on('drop', swayslot_on_drop); + }); {% endblock content %} diff --git a/templates/partials/negotiation-panel.html b/templates/partials/negotiation-panel.html index ad7c3fb..2010786 100644 --- a/templates/partials/negotiation-panel.html +++ b/templates/partials/negotiation-panel.html @@ -1 +1,3 @@ -@TODO Panel +
Negotiation {{negotiation.name}} is being run by {{room_owner_display_name}}
+The Market for this negotiation is {{room_owner_display_name}}
{% if room_owner_display_name == uid %}Change your display name
{% endif %}Market | ++ | + | + | + | + | + | + |
---|---|---|---|---|---|---|---|
+ |
+ As a favour
+ Contract is offered at the Demand price only and the client (market) earns a "- Rep" spot to use in future negotiations. + |
+
+ Buyer's Market
+ Contract is offered at the Demand price only. + |
+
+ At Value
+ Contract is offered at the value of Supply and Demand + |
+
+ Labour
+ Client agrees to add the Crew's "Break Point" in bounty to the value of Supply and Demand + |
+
+ Hazard Pay
+ Add one bounty per leg per taker to the value of the contract + |
+
+ 100% Markup
+ Double the cost of the Supply and Demand for the job + |
+
+ Expenses
+ The upkeep of items is factored into the value of the contract. Takers pay no upkeep this session. + |
+
Takers | ++ | + | + | + | + | + | + |