def get_own_area(country_id, timestamp=None): """ 領地を取得 :param country_id: :return: 成功 ? area : None """ try: db = DBSingleton() where = "country_id = " + str(country_id) # タイムスタンプによる条件 if timestamp is not None: datetime = BJTime.encode_to_sql(timestamp) where += " and timestamp >= \"" + datetime + "\"" result = db.select("col", "row", "type", "country_id", "food", "money", table="hex_grid", where=where) if len(result) == 0: return None else: return result except DBError as e: logging.error(e.message, detailed_error.get_error()) return None
def create_event(datetime, object, subject, event_name, data, status="ready"): """ イベントレコードを生成するヘルパー イベントレコードの形式を強要する :param datetime: 予定時間(必須) :param object: 主体 :param subject: 対象 :param event_name: イベントの種類名(必須) :param data: イベント用のデータ(str) :param status: レコードの状態 :return: 成功 ? event_record{datetime, ...} : None """ event_record = { "datetime": BJTime.encode_to_sql(datetime), "object": str(object), "subject": str(subject), "event_name": str(event_name), "data": str(data), "status": str(status) } if datetime is None or event_name is None: logging.error(event_record) return None return event_record
def get_unvisible_area(visibility, timestamp=None): """ 可視権から不可視範囲のグリッドを取得 :param visibility: :param [timestamp]: オプション。この時刻オブジェクト以降に変更されたグリッドのみ取得する :return: list[dict[col, row, type]] """ result = None try: # 可視権による条件 where = str(visibility) + " = 0" # タイムスタンプによる条件 if timestamp is not None: datetime = BJTime.encode_to_sql(timestamp) where += " and timestamp >= \"" + datetime + "\"" db = DBSingleton() result = db.select("col", "row", "type", table="hex_grid", where=where) except DBError as e: logging.error(e.message) raise Exception("DBエラー : visibilityが" + str(visibility) + u"の不可視範囲のグリッド取得失敗") return return result
def __start_new_year(cls): u"""新年度をスタートさせる""" # 情勢読み込み cls._game_settings["affair"].init() # マップ作製 cls._game_settings["map"].init() # TODO : この位置で開始させると他のイベントが入るまでcurrent_event_timeがNoneでループ回らない # 内政イベントチェーン開始 from lib.event.Sched.SchedDomestic import SchedEventDomestic from lib.DB import event_controller next_time = BJTime.add_time(BJTime.get_time_now(), 1000 * 10) event = SchedEventDomestic.create_recode(next_time) # 10秒後に開始 event_controller.add_event(event) GameMain.set_event_timestamp(next_time)
def loop(cls): u"""イベントループ スケジューラが空の時、スレッドはConditionでブロックされ、新しいスケジュールが追加されるまで待機する""" cls._logger.debug("loop() started") # イベントループ本体 while True: time.sleep(0.1) # 待機中のイベントがなければスキップ if cls._current_event_timestamp is None: continue # 最速のイベントの実行時間が過ぎていれば実行 if cls._current_event_timestamp <= BJTime.get_time_now(): # 予定時間が一番早いイベントをDBから参照 current_event_record = event_controller.peek_event() # 待機中のイベントがなければスキップ if current_event_record is None: continue # イベントレコードを削除 event_controller.remove_event(current_event_record["event_id"]) # イベントディスパッチ処理 event = None if current_event_record["event_name"] == "move": event = EventMove(current_event_record) elif current_event_record["event_name"] == "domestic": event = EventDomestic(current_event_record) elif current_event_record["event_name"] == "sched_domestic": event = SchedDomestic.SchedEventDomestic( current_event_record) try: event.run() except NotImplementedError: cls._logger.error("実装されていないイベントが呼び出された", detailed_error.get_error()) except Exception as e: cls._logger.error(e, detailed_error.get_error()) # 次に待機しているイベントがあれば予定時間をセット next_event_record = event_controller.peek_event() print("next_event_record=", next_event_record) if next_event_record is not None: cls._current_event_timestamp = next_event_record[ "datetime"] else: cls.set_event_timestamp(None) cls._logger.info("loop() ended")
def update_user_before_move(user_id, wait_untill): """ 移動に伴うユーザー情報の更新 :param user_id: :param wait_untill: 到着予定時刻を表す時刻オブジェクト :param status: 移動中の状態 :return: 更新成功 ? : Exception """ try: db = DBSingleton() query = "update user set" + \ " wait_untill = \"" + BJTime.encode_to_sql(wait_untill)+ "\"" \ " where user_id = \"" + str(user_id) + "\"" result = db.exec(query) except DBError as e: logging.error(e.message) raise Exception("DBエラー : 移動前のユーザー情報更新失敗")
def run(self): """ 内政をする :return: 成功 ? True : False """ self._logger.debug("enter EventDomestic run") self._logger.debug("event_record=", self._event_record) # 例外処理用 user_id = None division_id = None try: # デコード if self._decode_recode() is False: raise Exception("デコード失敗") return False # プレイヤー、部隊情報 player_info = player_controller.get_playerinfo_by_id(self._user_id) division_info = division_controller.get_division_info( self._division_id) # 内政中でないならキャンセル(本来はイベントレコード自体がキャンセルされるべき if player_info["status"] != "domestic": raise Exception("プレイヤーの状態がdomesticではない") return # 移動中でないならキャンセル(本来はイベントレコード自体がキャンセルされるべき) if division_info["status"] != "domestic": raise Exception("部隊の状態がdomesticではない") return # エラーに備えて本処理前にプレイヤーと部隊の状態を初期化する player_controller.update_user_status(self._user_id, "ready") division_controller.update_division_status(self._division_id, "ready") # 現在時刻 now = BJTime.get_time_now() # ヘックス情報 hex = hexgrid_controller.get_hexinfo(self._domestic_col, self._domestic_row) # 対象ヘックスの国籍が変わっていれば失敗 if hex["country_id"] != player_info["country_id"]: BJSocketHandler.BJSocketHandler.send_member_by_id( player_info["user_id"], { "event": "cancel", "data": { "title": "内政キャンセル", "reason": "対象ヘックスの国籍が違う" } }) return # 内政実行 # TODO : ステータスなどに合わせた変化 gm = GameMain.GameMain() message = "(" + str(self._domestic_col) + "," + str( self._domestic_row) + ")\n " # 結果メッセージ isSuccessed = False # 最大値増量 if self._type == "foster_food": new_food = gm.get_affair().get_op_food_lv() + hex["food"] if not hexgrid_controller.update_hex( hex["col"], hex["row"], food=new_food): message = message + "エラー。農業生産失敗" else: message = message + "農業生産 " + str( hex["food"]) + " → " + str(new_food) isSuccessed = True elif self._type == "foster_money": new_money = gm.get_affair().get_op_food_lv() + hex["money"] if not hexgrid_controller.update_hex( hex["col"], hex["row"], money=new_money): message = message + "エラー。商業生産失敗" else: message = message + "商業生産 " + str( hex["money"]) + " → " + str(new_money) isSuccessed = True # 徴税 elif self._type == "get_food": new_food = hex["food"] + division_info["food"] if not division_controller.update_division( division_info["division_id"], food=new_food): message = message + "エラー。農業徴税失敗" else: message = message + "領地から" + str( hex["food"]) + "の食糧を部隊に徴税した" isSuccessed = True elif self._type == "get_money": new_money = hex["money"] + division_info["money"] if not division_controller.update_division( division_info["division_id"], money=new_money): message = message + "エラー。商業徴税失敗" else: message = message + "領地から" + str( hex["money"]) + "の資金を部隊に徴税した" isSuccessed = True # 内政に失敗していればメッセージ送信 if not isSuccessed: BJSocketHandler.BJSocketHandler.send_member_by_id( player_info["user_id"], { "event": "cancel", "data": { "title": "内政失敗", "reason": "不明な内政種類" } }) return # 内政に成功。結果をプレイヤーに通知 BJSocketHandler.BJSocketHandler.send_member_by_id( player_info["user_id"], { "event": "notify", "data": { "title": "内政成功", "message": message } }) except DBError as e: logging.error("EventMove::run: caught DBError: " + e.message) except Exception as e: # 状態を初期化しておく player_controller.update_user_status(user_id, "ready") division_controller.update_division_status(division_id, "ready") logging.error(e) # プレイヤーにエラーを通知 payload = { "event": "cancel", "data": { "title": "内政キャンセル", "reason": "内部エラー" } } BJSocketHandler.BJSocketHandler.send_member_by_id( self._user_id, payload)
def run(self): """ プレイヤーを移動させる 終了後はmove_playerイベントを送信する :return: 成功 ? True : False """ self._logger.debug("enter run") self._logger.debug("event_record=", self._event_record) # 例外処理用 user_id = None division_id = None try: # デコード if self._decode_recode() is False: raise Exception("デコード失敗") return False # プレイヤー情報 player = player_controller.get_playerinfo_by_id(self._user_id) user_id = player["user_id"] moving_player_info = { "user_id": player["user_id"], "country_id": player["country_id"], "ex_col": player["col"], "ex_row": player["row"], "new_col": self._dest_col, "new_row": self._dest_row, "icon": player["icon_id"] } # 移動中でないならキャンセル(本来はイベントレコード自体がキャンセルされるべき if player["status"] != "moving": raise Exception("プレイヤーの状態がmovingではない") return # 部隊情報 division = division_controller.get_division_info( player["division_id"]) # 移動中でないならキャンセル(本来はイベントレコード自体がキャンセルされるべき) if division["status"] != "moving": raise Exception("部隊の状態がmovingではない") return # エラーに備えて本処理前にプレイヤーと部隊の状態を初期化する player_controller.update_user_status(self._user_id, "ready") division_controller.update_division_status(self._division_id, "ready") # 現在時刻 now = BJTime.get_time_now() """ 移動先に既に他の部隊がいる場合 """ division_dest = division_controller.get_division_info_by_colrow( self._dest_col, self._dest_row) if division_dest: # 味方なら移動キャンセルイベント if division_dest["country_id"] == player["country_id"]: data = { "event": "cancel", "data": { "reason": "移動先に味方の部隊がいるため、移動はキャンセルされた" } } BJSocketHandler.BJSocketHandler.send_member_by_id( player["user_id"], data) return # 敵なら戦闘開始 if division_dest["country_id"] != player["country_id"]: return """ 移動 """ # 移動前の可視領域減算 if not hexgrid_controller.update_visible_area( visibility=player["visibility"], division_id=division["division_id"], switch=False): raise Exception("可視領域減算に失敗") # 移動(部隊) if not division_controller.move_division( self._division_id, self._dest_col, self._dest_row): self._logger.error("部隊の移動に失敗") raise Exception("部隊の移動に失敗") # 移動(プレイヤー) if not player_controller.move_user(self._user_id, self._dest_col, self._dest_row): self._logger.error("プレイヤーの移動に失敗") raise Exception("プレイヤーの移動に失敗") # 移動後のヘックスを国の支配下に if not hexgrid_controller.update_hex( self._dest_col, self._dest_row, country_id=player["country_id"]): self._logging.error("移動後の支配に失敗") raise Exception("移動後の支配に失敗") # 移動後の可視領域可算 if not hexgrid_controller.update_visible_area( visibility=player["visibility"], division_id=division["division_id"], switch=True): self._logging.error("移動後の可視領域可算に失敗") raise Exception("移動後の可視領域可算に失敗") # 可視範囲を共有する通信中のプレイヤー(移動するプレイヤーも含む)にプレイヤーの移動を通知 notify_move_player(moving_player_info, player["visibility"], now) except DBError as e: logging.error("EventMove::run: caught DBError: " + e.message) except Exception as e: # 状態を初期化しておく player_controller.update_user_status(user_id, "ready") division_controller.update_division_status(division_id, "ready") logging.error(e) # プレイヤーにエラーを通知 payload = {"event": "error", "data": {"message": "進軍はキャンセルされた"}} BJSocketHandler.send_member_by_id(self._user_id, payload)
:param user_id: 移動するユーザー :param division_id: 移動する部隊 :param dest_col: 目的地col :param dest_row: 目的地row :param datetime: 到着時刻 :return: event_record[event_id, ...] """ # データ部 # "division_id,col,row" data = str(division_id) + "," + str(dest_col) + "," + str(dest_row) event = event_controller.create_event(datetime=datetime, object=user_id, subject="none", event_name="move", data=data) return event if __name__ == "__main__": date = BJTime.get_time_now() print(date) e = EventMove.create_recode(user_id="u_id", division_id="div_id", dest_col="1", dest_row="2", datetime=date) print(e)
def request_domestic(_cls, _self, data): """ 内政要求 """ payload = {"event": "response_ask_domestic", "data": {}} player_info = player_controller.get_playerinfo_by_id( _self.get_secure_cookie("user_id").decode('utf-8')) col = data["col"] row = data["row"] # プレイヤーは行動可能か if not player_info["status"] == "ready": _cls.send_member_by_id(player_info["user_id"], { "event": "cancel", "data": { "title": "内政キャンセル", "reason": "行動中" } }) return # 内政可能な半径か adjacent_area = hexgrid_controller.get_adjacent_area( player_info["col"], player_info["row"]) if not (col == player_info["col"] and row == player_info["row"]) and not ({ "col": col, "row": row } in adjacent_area): _cls.send_member_by_id( player_info["user_id"], { "event": "cancel", "data": { "title": "内政キャンセル", "reason": "内政可能な半径ではない" } }) return # 現在のヘックスの状態 target_hex = hexgrid_controller.get_hexinfo(col, row) if target_hex == None: _cls.send_member_by_id( player_info["user_id"], { "event": "cancel", "data": { "title": "内政キャンセル", "reason": "存在しないヘックス。" } }) # イベント登録 # TODO : プレイヤーのステータスなどによる処理 gm = GameMain() finish_time = BJTime.add_time(BJTime.get_time_now(), gm.get_affair().get_op_domestic_speed()) event = EventDomestic.create_recode(user_id=player_info["user_id"], division_id=player_info["division_id"], col=col, row=row, type=data["type"], datetime=finish_time) add_event(event) # プレイヤーと部隊の状態を変更 division_controller.update_division_status(player_info["division_id"], "domestic") player_controller.update_user_status(player_info["user_id"], "domestic") # メインエンジンにイベントの実行時を通知 gm = GameMain() gm.set_event_timestamp(finish_time)
def run(self): """ :return: 成功 ? True : False """ self._logger.debug("enter EventSchedDomestic run") self._logger.debug("event_record=", self._event_record) try: db = DBSingleton() # 可算前のの国情報取得 ex_countries = country_controller.get_all_countryinfo() # 全ての国で食糧可算 query = "UPDATE country" \ " SET food = ((SELECT" \ " SUM(hex_grid.food) from hex_grid" \ " WHERE hex_grid.country_id = country.country_id" \ " GROUP BY hex_grid.country_id) + country.food)" \ "WHERE EXISTS(SELECT 1 FROM hex_grid" \ " WHERE hex_grid.country_id = country.country_id);" db.exec(query) # 全ての国で商業可算 query = "UPDATE country" \ " SET money = ((SELECT" \ " SUM(hex_grid.money) from hex_grid" \ " WHERE hex_grid.country_id = country.country_id" \ " GROUP BY hex_grid.country_id) + country.money)" \ "WHERE EXISTS(SELECT 1 FROM hex_grid" \ " WHERE hex_grid.country_id = country.country_id);" db.exec(query) print("SchedDomestic run") # イベントをインターバルでチェーン gm = GameMain.GameMain() next_time = BJTime.add_time( BJTime.get_time_now(), gm.get_affair().get_domestic_interval()) next_event = SchedEventDomestic.create_recode(next_time) event_controller.add_event(next_event) gm.set_event_timestamp(next_time) # それぞれの国へ通知 new_countries = country_controller.get_all_countryinfo() for i in range(0, len(new_countries)): message = "食糧 " + str(new_countries[i]["food"]) + " → " + str( ex_countries[i]["food"]) + "\n" message += "資金 " + str( new_countries[i]["money"]) + " → " + str( ex_countries[i]["money"]) payload = { "event": "notify", "data": { "title": "徴税結果", "message": message } } BJSocketHandler.BJSocketHandler.send_member_by_country( new_countries["country_id"], payload) except DBError as e: logging.error("EventMove::run: caught DBError: " + e.message) except Exception as e: logging.error(e)
def request_move(_cls, _self, data): """ 行軍イベントをセットする :param _cls: BJSocketHandlerのクラス :param _self: BJSocketHandlerのインスタンス :param data: クライアントから受信したデータ :return: """ user_id = _self.get_secure_cookie("user_id").decode('utf-8') payload = {"event" : "response_request_move" , "data" : {}} try: """ move_queryの焼き増しな部分があるが、queryとrequestの時間差または不正防止のため 再度移動可能かどうか調べる """ # 目的地 dest_col = data["col"] dest_row = data["row"] # プレイヤーの状態 user_id = _self.get_secure_cookie("user_id").decode('utf-8') player = get_playerinfo_by_id(user_id) # プレイヤーが待機中でないと移動できない if player["status"] != "ready": payload["data"] = {"response" : "deny", "reason" : "行動中"} _self.send_you(payload) return # 配下の部隊の状態 division = get_division_info(player["division_id"]) # 部隊がセットされていないと移動できない if not division: payload["data"] = {"response" : "deny", "reason" : "配下の師団がセットされていない"} _self.send_you(payload) # 部隊の移動半径内でないと移動できない movable_area = get_movable_area_by_division_id(division["division_id"], player["col"], player["row"]) required_time = False # 所要時間 for hex in movable_area: if hex["col"] == dest_col and hex["row"] == dest_row: required_time = hex["time"] if not required_time: payload["data"] = {"response" : "deny", "reason" : "[" + str(dest_col) + "," + str(dest_row) + "]は移動可能半径外"} _cls.send_player(user_id, payload) # 師団の兵科情報取得 branch_info = get_branch_info(division["branch_id"]) # ゲームレベルから設定値を取得 gm = GameMain() op_food_lv = gm.get_affair().get_op_food_lv() op_money_lv = gm.get_affair().get_op_money_lv() op_speed_lv = gm.get_affair().get_op_speed_lv() # 運用に必要な食糧と金 : 師団規模 * 兵科固定値 * ゲームレベル food_needed = division["quantity"] * branch_info["op_food"] * op_food_lv money_needed = division["quantity"] * branch_info["op_money"] * op_money_lv # 資金と食糧、それぞれ足りなければ移動不可 if division["food"] < food_needed: payload["data"] = {"response" : "deny", "reason" : "部隊の運用食糧が足りない。<br>" + "部隊の保持食糧 : " + str(division["food"]) + "<br>" + "必要な食糧 = 師団規模(" + division["quantity"] + ")×兵科補正(" + str(branch_info["op_food"]) + ")×情勢補正(" + str(op_food_lv) + ") = " + str(food_needed)} _self.send_you(payload) return False elif division["money"] < money_needed: payload["data"] = {"response" : "deny", "reason" : "部隊の運用資金が足りない。<br>" + "部隊の保持資金 : " + str(division["money"]) + "<br>" + "必要な資金 = 師団規模(" + division["quantity"] + ")×兵科補正(" + str(branch_info["op_money"]) + ")×情勢補正(" + str(op_money_lv) + ") = " + str(money_needed)} _self.send_you(payload) return False """ チェックが終わったのでプレイヤー情報等を更新し、行軍イベントをセットする """ # 到着予定時刻計算 required_time = required_time * op_speed_lv # ゲームレベル適用 current_time = BJTime.get_time_now() arrival_time = BJTime.add_time(current_time, required_time) # TODO: チェックと次の処理の間に状態が変更される可能性がある # 行軍のイベントレコードを作成 event_record = EventMove.create_recode(user_id=player["user_id"], division_id=division["division_id"], dest_col=dest_col, dest_row=dest_row, datetime=arrival_time) # レコードの作成に失敗していれば失敗 if event_record is None: payload["data"] = {"response" : "deny", "reason" : "イベントレコードの作成に失敗した。管理者に連絡して下さい。"} _self.send_you(payload) return # レコードをDBに登録 add_event(event_record) # メインエンジンにイベントの実行時を通知 gm = GameMain() gm.set_event_timestamp(arrival_time) # プレイヤーの状態を更新 update_user_before_move(user_id=player["user_id"], wait_untill=arrival_time) update_user_status(player["user_id"],"moving") # 部隊の状態を更新 update_division_before_move(division_id=division["division_id"], food=food_needed, money=money_needed) update_division_status(division["division_id"], "moving") # クライアントに通知 payload["data"] = { "response" : "approval", "arrival_time" : arrival_time.strftime("%Y-%m-%d %H:%M:%S")} _self.send_you(payload) return except Exception as e: logging.error(e) _self.send_error(e.message); return assert False