def get(self, player_id, namespace): """ Get full dump of game state for the current player in namespace 'namespace' """ can_edit_player(player_id) gamestates = g.db.query(GameState) \ .filter(GameState.player_id == player_id, GameState.namespace == namespace) \ .order_by(-GameState.gamestate_id) if gamestates.count() == 0: msg = "Gamestate '%s' for player %s not found" % (namespace, player_id) log.info(msg) abort(httplib.NOT_FOUND) elif gamestates.count() > 1: raise RuntimeError( "Player %s has %s game states with namespace '%s'" % (player_id, gamestates.count(), namespace)) gamestate = gamestates.first() ret = gamestate.as_dict() ret["gamestatehistory_url"] = url_for("gamestate.gamestatehistorylist", player_id=player_id, namespace=namespace, _external=True) return ret
def get(self, player_id): """ Get a list of all gamestates for the player """ can_edit_player(player_id) gamestates = g.db.query(GameState) \ .filter(GameState.player_id == player_id) \ .order_by(GameState.namespace) ret = [] for gamestate in gamestates: entry = { "namespace": gamestate.namespace, "gamestate_id": gamestate.gamestate_id, "gamestate_url": url_for("gamestate.gamestate", player_id=player_id, namespace=gamestate.namespace, _external=True) } ret.append(entry) return ret
def get(self, player_id, namespace): can_edit_player(player_id) rows = g.db.query(GameStateHistory) \ .filter(GameStateHistory.player_id == player_id, GameStateHistory.namespace == namespace) \ .order_by(-GameStateHistory.gamestatehistory_id) if not rows: abort(httplib.NOT_FOUND) ret = [] for row in rows: entry = { "gamestatehistory_id": row.gamestatehistory_id, "gamestatehistoryentry_url": url_for("gamestate.gamestatehistoryentry", player_id=player_id, namespace=namespace, gamestatehistory_id=row.gamestatehistory_id, _external=True), "create_date": row.create_date } ret.append(entry) return ret
def get(self, player_id): """ Get a list of outstanding tickets for the player """ can_edit_player(player_id) tickets = g.db.query(Ticket) \ .filter(Ticket.player_id == player_id, Ticket.used_date == None) ret = [add_ticket_links(t) for t in tickets] return ret
def get(self, player_id, journal_id): """ Get a specific journal entry for the player """ can_edit_player(player_id) entry = get_journal_entry(player_id, journal_id) if not entry.first(): return json_response("Journal entry not found", httplib.NOT_FOUND) ret = entry.first().as_dict() return ret
def get(self, player_id, namespace, gamestatehistory_id): can_edit_player(player_id) row_gamestate = g.db.query(GameStateHistory)\ .filter(GameStateHistory.player_id == player_id, GameStateHistory.gamestatehistory_id == gamestatehistory_id) \ .first() if not row_gamestate: abort(httplib.NOT_FOUND) ret = row_gamestate.as_dict() return ret
def get(self, player_id): """ """ can_edit_player(player_id) if not get_player(player_id): abort(httplib.NOT_FOUND) summary = g.db.query(PlayerSummary).filter( PlayerSummary.player_id == player_id) ret = {} for row in summary: ret[row.name] = row.value return ret
def _patch(self, player_id, ticket_id): """ Claim a ticket """ args = request.json journal_id = args.get("journal_id") if not can_edit_player(player_id): abort(httplib.METHOD_NOT_ALLOWED, message="That is not your player!") ticket = get_ticket(player_id, ticket_id) if not ticket: abort(404, message="Ticket was not found") if ticket.used_date: abort(404, message="Ticket has already been claimed") log.info( "Player %s is claiming ticket %s of type '%s' with journal_id %s", player_id, ticket_id, ticket.ticket_type, journal_id) ticket.used_date = datetime.datetime.utcnow() ticket.journal_id = journal_id g.db.commit() log_event(player_id, "event.player.ticketclaimed", {"ticket_id": ticket_id}) return add_ticket_links(ticket)
def delete(self, player_id, namespace): """ Remove a gamestate from the player (it will still exist in the history table) """ can_edit_player(player_id) gamestates = g.db.query(GameState) \ .filter(GameState.player_id == player_id, GameState.namespace == namespace) gamestates.delete() g.db.commit() log.info("Gamestate '%s' for player %s has been deleted", namespace, player_id) return "OK"
def get(self, player_id, ticket_id): """ Get information about any past or ongoing battle initiated by the current player against the other player """ if not can_edit_player(player_id): abort(httplib.METHOD_NOT_ALLOWED, message="That is not your player!") ticket = get_ticket(player_id, ticket_id) if not ticket: abort(404, message="Ticket was not found") return add_ticket_links(ticket)
def get(self, player_id): """ Get a list of recent journal entries for the player """ DEFAULT_ROWS = 100 args = self.get_args.parse_args() can_edit_player(player_id) # TODO: Custom filters query = g.db.query(PlayerJournal) query = query.filter(PlayerJournal.player_id == player_id) if not getattr(args, "include_deleted", False): query = query.filter(PlayerJournal.deleted == False) query = query.order_by(-PlayerJournal.journal_id, -PlayerJournal.sequence_id) query = query.limit(args.rows or DEFAULT_ROWS) ret = [] for entry in query: e = entry.as_dict() ret.append(e) return ret
def put(self, player_id): """ Full update of summary fields, deletes fields from db that are not included """ if not can_edit_player(player_id): abort(httplib.METHOD_NOT_ALLOWED, message="That is not your player!") if not get_player(player_id): abort(httplib.NOT_FOUND) old_summary = g.db.query(PlayerSummary).filter( PlayerSummary.player_id == player_id).all() new_summary = [] updated_ids = set() for name, val in request.json.iteritems(): for row in old_summary: if row.name == name: updated_ids.add(row.id) if val != row.value: row.value = val break else: log.info("Adding a new summary field, '%s' with value '%s'", name, val) summary_row = PlayerSummary(player_id=player_id, name=name, value=val) g.db.add(summary_row) g.db.flush() updated_ids.add(summary_row.id) g.db.commit() for row in old_summary: if row.id not in updated_ids: log.info( "Deleting summary field '%s' with id %s which had the value '%s'", row.name, row.id, row.value) g.db.delete(row) g.db.commit() new_summary = g.db.query(PlayerSummary).filter( PlayerSummary.player_id == player_id).all() request_txt = "" for k, v in request.json.iteritems(): request_txt += "%s = %s, " % (k, v) if request_txt: request_txt = request_txt[:-2] new_summary_txt = "" for row in new_summary: new_summary_txt += "%s = %s, " % (row.name, row.value) if new_summary_txt: new_summary_txt = new_summary_txt[:-2] log.info( "Updating summary for player %s. Request is '%s'. New summary is '%s'", player_id, request_txt, new_summary_txt) ret = [] return ret
def patch(self, player_id): """ Partial update of summary fields. """ if not can_edit_player(player_id): abort(httplib.METHOD_NOT_ALLOWED, message="That is not your player!") if not get_player(player_id): abort(httplib.NOT_FOUND) old_summary = g.db.query(PlayerSummary).filter( PlayerSummary.player_id == player_id).all() old_summary_txt = "" for row in old_summary: old_summary_txt += "%s = %s, " % (row.name, row.value) changes = {} for name, val in request.json.iteritems(): for row in old_summary: if row.name == name: if val != row.value: changes[name] = {"old": row.value, "new": val} row.value = val break else: log.info("Adding a new summary field, '%s' with value '%s'", name, val) changes[name] = {"old": None, "new": val} summary_row = PlayerSummary(player_id=player_id, name=name, value=val) g.db.add(summary_row) # if this summary stat changes we write it into our history log if name in changes: summaryhistory_row = PlayerSummaryHistory(player_id=player_id, name=name, value=val) g.db.add(summaryhistory_row) g.db.commit() new_summary = g.db.query(PlayerSummary).filter( PlayerSummary.player_id == player_id).all() log_event(player_id, "event.player.summarychanged", changes) request_txt = "" for k, v in request.json.iteritems(): request_txt += "%s = %s, " % (k, v) if request_txt: request_txt = request_txt[:-2] if old_summary_txt: old_summary_txt = old_summary_txt[:-2] new_summary_txt = "" for row in new_summary: new_summary_txt += "%s = %s, " % (row.name, row.value) if new_summary_txt: new_summary_txt = new_summary_txt[:-2] log.info( "Updating summary. Request is '%s'. Old summary is '%s'. New summary is '%s'", request_txt, old_summary_txt, new_summary_txt) return [r.as_dict() for r in new_summary]
def post(self, player_id): """ Add a journal entry """ if not can_edit_player(player_id): abort(httplib.METHOD_NOT_ALLOWED, message="That is not your player!") args_list = request.json if not isinstance(args_list, list): raise RuntimeError("Arguments should be a list") for a in args_list: if "journal_id" not in a: abort(httplib.BAD_REQUEST) ret = [] now = datetime.datetime.utcnow() MAX_DRIFT = 60 args_list.sort(key=itemgetter('journal_id')) client_current_time = args_list[0].get("client_current_time") if not client_current_time: log.warning("Client is uploading journal entries without a client_current_time") else: client_current_time = parser.parse(client_current_time) diff = (client_current_time.replace(tzinfo=None) - now).total_seconds() if abs(diff) > MAX_DRIFT: log.warning("Client's clock is %.0f seconds out of sync. " "Client system time: '%s', Server time: '%s'", diff, client_current_time, now) for args in args_list: # Special handling if this is a rollback event. # We mark all journal entries higher than the event to rollback to # as deleted (as an optimization) and then add the rollback event itself. if "rollback_to_journal_id" in args: to_journal_id = int(args.get("rollback_to_journal_id")) # TODO: Check if there are any gamestates persisted after the id gamestate = get_player_gamestate(player_id) if not gamestate: log.warning("Player is rebasing journal entries but doesn't" "have any gamestate.") elif gamestate.journal_id > to_journal_id: return json_response("Journal has already been persisted into home base!", httplib.BAD_REQUEST) entry = get_journal_entry(player_id, to_journal_id) if not entry.first(): log.warning("Rolling back to journal entry %s which doesn't exist") elif entry.first().deleted: log.warning("Rolling back to journal entry %s which has been rolled back") g.db.query(PlayerJournal).filter(PlayerJournal.player_id == player_id, PlayerJournal.journal_id > to_journal_id) \ .update({"deleted": True}) # report if the client's clock is out of sync with the server timestamp = parser.parse(args["timestamp"]) diff = (timestamp.replace(tzinfo=None) - now).total_seconds() if abs(diff) > MAX_DRIFT: log.info("Client is sending journal info for journal entry %s '%s' which " "is %.0f seconds out of sync. " "Client journal timestamp: '%s', Server timestamp: '%s'", args["journal_id"], args["action"], diff, args["timestamp"], now) try: journal = write_journal(player_id, args["action"], args["journal_id"], args["timestamp"], details=args.get("details"), steps=args.get("steps"), actor_id=current_user["player_id"]) except JournalError as e: # TODO: We now reject the journal entry and subsequent entries instead of # rolling the entire thing back. Is that what we want? log.warning("Error writing to journal. Rejecting entry. Error was: %s", e) abort(httplib.BAD_REQUEST, description=e.message) ret.append({"journal_id": journal["journal_id"], "url": url_for("journal.entry", player_id=player_id, journal_id=journal["journal_id"]) }) return ret, httplib.CREATED
def put(self, player_id, namespace): """ Upload the gamestate state to the server """ can_edit_player(player_id) args = request.json data = args["gamestate"] journal_id = None if args.get("journal_id"): journal_id = int(args["journal_id"]) if journal_id: journal_row = g.db.query(PlayerJournal) \ .filter(PlayerJournal.player_id == player_id, PlayerJournal.journal_id == journal_id, PlayerJournal.deleted != True) \ .first() if not journal_row: # Note: this might happen normally unless we serialize on the # client to ensure all journal entries # are acked before sending up the new state msg = "Journal entry %s for player %s not found!" % ( journal_id, player_id) log.warning(msg) return json_response(msg, httplib.BAD_REQUEST) gamestate = g.db.query(GameState)\ .filter(GameState.player_id == player_id, GameState.namespace == namespace) \ .order_by(-GameState.gamestate_id).first() if gamestate: if journal_id and journal_id <= gamestate.journal_id: # TODO: Raise here? log.warning( "Writing a new gamestate with an older journal_id, %s " "than the current one", journal_id, extra=gamestate.as_dict()) gamestate.version += 1 gamestate.data = data gamestate.journal_id = journal_id log.info( "Updated gamestate for player %s to version %s. journal_id = %s", player_id, gamestate.version, journal_id) else: gamestate = GameState(player_id=player_id, data=data, journal_id=journal_id, namespace=namespace) g.db.add(gamestate) g.db.flush() log.info("Added new gamestate for player") # write new gamestate to the history table for safe keeping gamestatehistory_row = GameStateHistory( player_id=player_id, version=gamestate.version, data=gamestate.data, namespace=gamestate.namespace, journal_id=gamestate.journal_id) g.db.add(gamestatehistory_row) g.db.flush() gamestatehistory_id = gamestatehistory_row.gamestatehistory_id gamestate.gamestatehistory_id = gamestatehistory_id g.db.commit() return gamestate.as_dict()