改写战斗中替换group的逻辑

This commit is contained in:
Elaina 2024-10-13 01:24:58 +08:00
commit 7f89eb0db8
3890 changed files with 82290 additions and 0 deletions

788
mower/utils/operators.py Normal file
View file

@ -0,0 +1,788 @@
import copy
from datetime import datetime, timedelta
from evalidate import Expr, base_eval_model
from mower.utils.plan import PlanConfig
from ..data import agent_arrange_order, agent_list, base_room_list
from ..solvers.infra.record import save_action_to_sqlite_decorator
from ..utils.log import logger
class SkillUpgradeSupport:
support_class = None
level = 1
efficiency = 0
half_off = False
add_on = False
match = False
use_booster = True
name = ""
swap_name = ""
def __init__(self, name, skill_level, efficiency, match, swap_name="艾丽妮"):
self.name = name
self.level = skill_level
self.efficiency = efficiency
self.match = match
if self.level > 1:
self.half_off = True
self.swap_name = swap_name
class Operators:
config = None
operators = None
exhaust_agent = []
exhaust_group = []
groups = None
dorm = []
plan = None
global_plan = None
plan_condition = []
shadow_copy = {}
current_room_changed_callback = None
first_init = True
skill_upgrade_supports = []
def __init__(self, plan):
self.operators = {}
self.groups = {}
self.exhaust_agent = []
self.exhaust_group = []
self.dorm = []
self.workaholic_agent = []
self.free_blacklist = []
self.global_plan = plan
self.backup_plans = plan["backup_plans"]
# 切换默认排班
self.swap_plan([False] * (len(self.backup_plans)))
self.run_order_rooms = {}
self.clues = []
self.current_room_changed_callback = None
self.party_time = None
self.eval_model = base_eval_model.clone()
self.eval_model.nodes.extend(["Call", "Attribute"])
self.eval_model.attributes.extend(
[
"operators",
"party_time",
"is_working",
"is_resting",
"current_mood",
"current_room",
]
)
def __repr__(self):
return f"Operators(operators={self.operators})"
def calculate_switch_time(self, support: SkillUpgradeSupport):
hour = 0
half_off = support.half_off
level = support.level
match = support.match
efficiency = support.efficiency
same = support.name == support.swap_name
if level == 1:
half_off = False
# if left_minutes > 0 or left_hours > 0:
# hour = left_minutes / 60 + left_hours
# 基本5%
basic = 5
if support.add_on:
# 阿斯卡伦
basic += 5
if hour == 0:
hour = level * 8
if half_off:
hour = hour / 2
left = 0
if not same:
left = 5 * (100 + basic + (30 if match else 0)) / 100
left = hour - left
else:
left = hour
return left * 100 / (100 + efficiency + basic)
def swap_plan(self, condition, refresh=False):
self.plan = copy.deepcopy(self.global_plan["default_plan"].plan)
self.config: PlanConfig = copy.deepcopy(self.global_plan["default_plan"].config)
for index, success in enumerate(condition):
if success:
self.plan, self.config = self.merge_plan(index, self.config, self.plan)
self.plan_condition = condition
if refresh:
self.first_init = True
error = self.init_and_validate(True)
self.first_init = False
if error:
return error
def merge_plan(self, idx, ext_config, default_plan=None):
if default_plan is None:
default_plan = copy.deepcopy(self.global_plan["default_plan"].plan)
plan = copy.deepcopy(self.global_plan["backup_plans"][idx])
# 更新切换排班表
for key, value in plan.plan.items():
if key in default_plan:
for idx, operator in enumerate(value):
if operator.agent != "Current":
default_plan[key][idx] = operator
return default_plan, ext_config.merge_config(plan.config)
def generate_conditions(self, n):
if n == 1:
return [[True], [False]]
else:
prev_conditions = self.generate_conditions(n - 1)
conditions = []
for condition in prev_conditions:
conditions.append(condition + [True])
conditions.append(condition + [False])
return conditions
def init_and_validate(self, update=False):
self.groups = {}
self.exhaust_agent = []
self.exhaust_group = []
self.workaholic_agent = []
self.shadow_copy = copy.deepcopy(self.operators)
self.operators = {}
for room in self.plan.keys():
for idx, data in enumerate(self.plan[room]):
if data.agent not in list(agent_list.keys()) and data.agent != "Free":
return f"干员名输入错误: 房间->{room}, 干员->{data.agent}"
if data.agent in ["龙舌兰", "但书"]:
return f"高效组不可用龙舌兰,但书 房间->{room}, 干员->{data.agent}"
if data.agent == "菲亚梅塔" and idx == 1:
return f"菲亚梅塔不能安排在2号位置 房间->{room}, 干员->{data.agent}"
if data.agent == "菲亚梅塔" and not room.startswith("dorm"):
return "菲亚梅塔必须安排在宿舍"
if data.agent == "Free" and not room.startswith("dorm"):
return f"Free只能安排在宿舍 房间->{room}, 干员->{data.agent}"
if data.agent in self.operators and data.agent != "Free":
return f"高效组干员不可重复 房间->{room},{self.operators[data.agent].room}, 干员->{data.agent}"
self.add(
Operator(
data.agent,
room,
idx,
data.group,
data.replacement,
"high",
operator_type="high",
)
)
missing_replacements = []
for room in self.plan.keys():
if room.startswith("dorm") and len(self.plan[room]) != 5:
return f"宿舍 {room} 人数少于5人"
for idx, data in enumerate(self.plan[room]):
# 菲亚梅塔替换组做特例判断
if "龙舌兰" in data.replacement and "但书" in data.replacement:
return f"替换组不可同时安排龙舌兰和但书 房间->{room}, 干员->{data.agent}"
if "菲亚梅塔" in data.replacement:
return f"替换组不可安排菲亚梅塔 房间->{room}, 干员->{data.agent}"
r_count = len(data.replacement)
if "龙舌兰" in data.replacement or "但书" in data.replacement:
r_count -= 1
if r_count <= 0 and (
(data.agent != "Free" and (not room.startswith("dorm")))
or data.agent == "菲亚梅塔"
):
missing_replacements.append(data.agent)
for _replacement in data.replacement:
if (
_replacement not in list(agent_list.keys())
and data.agent != "Free"
):
return f"干员名输入错误: 房间->{room}, 干员->{_replacement}"
if data.agent != "菲亚梅塔":
# 普通替换
if (
_replacement in self.operators
and self.operators[_replacement].is_high()
):
return f"替换组不可用高效组干员: 房间->{room}, 干员->{_replacement}"
self.add(Operator(_replacement, ""))
else:
if _replacement not in self.operators:
return f"菲亚梅塔替换不在高效组列: 房间->{room}, 干员->{_replacement}"
if (
_replacement in self.operators
and not self.operators[_replacement].is_high()
):
return f"菲亚梅塔替换只能为高效组干员: 房间->{room}, 干员->{_replacement}"
# 判定替换缺失
if "菲亚梅塔" in missing_replacements:
return "菲亚梅塔替换缺失"
if len(missing_replacements):
return f'以下干员替换组缺失:{",".join(missing_replacements)}'
dorm_names = [k for k in self.plan.keys() if k.startswith("dorm")]
dorm_names.sort(key=lambda d: d, reverse=False)
added = []
# 竖向遍历出效率高到低
if not update:
for dorm in dorm_names:
free_found = False
for _idx, _dorm in enumerate(self.plan[dorm]):
if _dorm.agent == "Free" and _idx <= 1:
if "波登可" not in [_agent.agent for _agent in self.plan[dorm]]:
return "宿舍必须安排2个宿管"
if _dorm.agent != "Free" and free_found:
return "Free必须连续且安排在宿管后"
if (
_dorm.agent == "Free"
and not free_found
and (dorm + str(_idx)) not in added
and len(added) < self.config.max_resting_count
):
self.dorm.append(Dormitory((dorm, _idx)))
added.append(dorm + str(_idx))
free_found = True
continue
if not free_found:
return "宿舍必须安排至少一个Free"
# VIP休息位用完后横向遍历
for dorm in dorm_names:
for _idx, _dorm in enumerate(self.plan[dorm]):
if _dorm.agent == "Free" and (dorm + str(_idx)) not in added:
self.dorm.append(Dormitory((dorm, _idx)))
added.append(dorm + str(_idx))
else:
for key, value in self.shadow_copy.items():
if key not in self.operators:
self.add(Operator(key, ""))
if len(self.dorm) < self.config.max_resting_count:
return f"宿舍Free总数 {len(self.dorm)}小于最大分组数 {self.config.max_resting_count}"
# 跑单
for x, y in self.plan.items():
if not x.startswith("room"):
continue
if any(
("但书" in obj.replacement or "龙舌兰" in obj.replacement) for obj in y
):
self.run_order_rooms[x] = {}
# 判定分组排班可能性
current_high = self.config.max_resting_count
current_low = len(self.dorm) - self.config.max_resting_count
for key in self.groups:
high_count = 0
low_count = 0
_replacement = []
for name in self.groups[key]:
_candidate = next(
(
r
for r in self.operators[name].replacement
if r not in _replacement and r not in ["龙舌兰", "但书"]
),
None,
)
if _candidate is None:
return f"{key} 分组无法排班,替换组数量不够"
else:
_replacement.append(_candidate)
if self.operators[name].workaholic:
continue
if self.operators[name].resting_priority == "high":
high_count += 1
else:
low_count += 1
if high_count > current_high or low_count > current_low:
return f"{key} 分组无法排班,宿舍可用高优先{current_high},低优先{current_low}->分组需要高优先{high_count},低优先{low_count}"
# 设定令夕模式的心情阈值
self.init_mood_limit()
for name in self.workaholic_agent:
if name not in self.config.free_blacklist:
self.config.free_blacklist.append(name)
logger.info("宿舍黑名单:" + str(self.config.free_blacklist))
def set_mood_limit(self, name, upper_limit=24, lower_limit=0):
if name in self.operators:
self.operators[name].upper_limit = upper_limit
self.operators[name].lower_limit = lower_limit
logger.info(f"自动设置{name}心情下限为{lower_limit},上限为{upper_limit}")
def init_mood_limit(self):
# 设置心情阈值 for 夕,令,
if self.config.ling_xi == 1:
self.set_mood_limit("", upper_limit=12)
self.set_mood_limit("", lower_limit=12)
elif self.config.ling_xi == 2:
self.set_mood_limit("", upper_limit=12)
self.set_mood_limit("", lower_limit=12)
elif self.config.ling_xi == 0:
self.set_mood_limit("")
self.set_mood_limit("")
# 设置同组心情阈值
finished = []
for name in ["", ""]:
if (
name in self.operators
and self.operators[name].group != ""
and self.operators[name].group not in finished
):
for group_name in self.groups[self.operators[name].group]:
if group_name not in ["", ""]:
if self.config.ling_xi in [1, 2]:
self.set_mood_limit(group_name, lower_limit=12)
elif self.config.ling_xi == 0:
self.set_mood_limit(group_name, lower_limit=0)
finished.append(self.operators[name].group)
# 设置铅踝心情阈值
# 三种情况:
# 1. 铅踝不是主力:不管
# 2. 铅踝是红云组主力,设置心情上限 12、下限 8,效率 37%
# 3. 铅踝是普通主力:设置心情下限 20,效率 30%
TOTTER = "铅踝"
VERMEIL = "红云"
if TOTTER in self.operators and self.operators[TOTTER].operator_type == "high":
if (
VERMEIL in self.operators
and self.operators[VERMEIL].operator_type == "high"
and self.operators[VERMEIL].room == self.operators[TOTTER].room
):
self.set_mood_limit(TOTTER, upper_limit=12, lower_limit=8)
else:
self.set_mood_limit(TOTTER, upper_limit=24, lower_limit=20)
def evaluate_expression(self, expression):
try:
result = Expr(expression, self.eval_model).eval({"op_data": self})
return result
except Exception as e:
logger.exception(f"Error evaluating expression: {e}")
return None
def get_current_room(self, room, bypass=False, current_index=None):
room_data = {
v.current_index: v
for k, v in self.operators.items()
if v.current_room == room
}
res = [obj.agent for obj in self.plan[room]]
not_found = False
for idx, op in enumerate(res):
if idx in room_data:
res[idx] = room_data[idx].name
else:
res[idx] = ""
if current_index is not None and idx not in current_index:
continue
not_found = True
if not_found and not bypass:
return None
else:
return res
def predict_fia(self, operators, fia_mood, hours=240):
recover_hours = (24 - fia_mood) / 2
for agent in operators:
agent.mood -= agent.depletion_rate * recover_hours
if agent.mood < 0.0:
return False
if recover_hours >= hours or 0 < recover_hours < 1:
return True
operators.sort(
key=lambda x: (x.mood - x.lower_limit) / (x.upper_limit - x.lower_limit),
reverse=False,
)
fia_mood = operators[0].mood
operators[0].mood = 24
return self.predict_fia(operators, fia_mood, hours - recover_hours)
def reset_dorm_time(self):
for name in self.operators.keys():
agent = self.operators[name]
if agent.room.startswith("dorm"):
agent.time_stamp = None
@save_action_to_sqlite_decorator
def update_detail(self, name, mood, current_room, current_index, update_time=False):
agent = self.operators[name]
if update_time:
if agent.time_stamp is not None and agent.mood > mood:
agent.depletion_rate = (
(agent.mood - mood)
* 3600
/ ((datetime.now() - agent.time_stamp).total_seconds())
)
agent.time_stamp = datetime.now()
# 如果移出宿舍,则清除对应宿舍数据 且重新记录高效组心情(如果有备用班,则跳过高效组判定)
if (
agent.current_room.startswith("dorm")
and not current_room.startswith("dorm")
and (agent.is_high() or self.backup_plans)
):
self.refresh_dorm_time(
agent.current_room, agent.current_index, {"agent": ""}
)
if update_time:
self.time_stamp = datetime.now()
else:
self.time_stamp = None
agent.depletion_rate = 0
if (
self.get_dorm_by_name(name)[0] is not None
and not current_room.startswith("dorm")
and (agent.is_high() or self.backup_plans)
):
_dorm = self.get_dorm_by_name(name)[1]
_dorm.name = ""
_dorm.time = None
agent.current_room = current_room
agent.current_index = current_index
agent.mood = mood
# 如果是高效组且没有记录时间,则返还index
if agent.current_room.startswith("dorm") and (
agent.is_high() or self.backup_plans
):
for dorm in self.dorm:
if (
dorm.position[0] == current_room
and dorm.position[1] == current_index
and dorm.time is None
):
return current_index
if agent.name == "菲亚梅塔" and (
self.operators["菲亚梅塔"].time_stamp is None
or self.operators["菲亚梅塔"].time_stamp < datetime.now()
):
return current_index
def refresh_dorm_time(self, room, index, agent):
for idx, dorm in enumerate(self.dorm):
# Filter out resting priority low
# if idx >= self.config.max_resting_count:
# break
if dorm.position[0] == room and dorm.position[1] == index:
# 如果人为高效组,则记录时间
_name = agent["agent"]
if _name in self.operators.keys() and (
self.operators[_name].is_high() or self.config.free_room
):
dorm.name = _name
_agent = self.operators[_name]
# 如果干员有心情上限,则按比例修改休息时间
if _agent.mood != 24:
sec_remaining = (
(_agent.upper_limit - _agent.mood)
* ((agent["time"] - _agent.time_stamp).total_seconds())
/ (24 - _agent.mood)
)
dorm.time = _agent.time_stamp + timedelta(seconds=sec_remaining)
else:
dorm.time = agent["time"]
elif _name in list(agent_list.keys()):
dorm.name = _name
dorm.time = agent["time"]
break
def correct_dorm(self):
for idx, dorm in enumerate(self.dorm):
if dorm.name != "" and dorm.name in self.operators.keys():
op = self.operators[dorm.name]
if not (
dorm.position[0] == op.current_room
and dorm.position[1] == op.current_index
):
self.dorm[idx].name = ""
self.dorm[idx].time = None
else:
if (
self.dorm[idx].time is not None
and self.dorm[idx].time < datetime.now()
):
op.mood = op.upper_limit
op.time_stamp = self.dorm[idx].time
logger.debug(
f"检测到{op.name}心情恢复满,设置心情至{op.upper_limit}"
)
def get_train_support(self):
for name in self.operators.keys():
agent = self.operators[name]
if agent.current_room == "train" and agent.current_index == 0:
return agent.name
return None
def get_refresh_index(self, room, plan):
ret = []
if room.startswith("dorm") and self.config.free_room:
return [i for i, x in enumerate(self.plan[room]) if x == "Free"]
for idx, dorm in enumerate(self.dorm):
# Filter out resting priority low
if idx >= self.config.max_resting_count:
if not self.config.free_room:
break
if dorm.position[0] == room:
for i, _name in enumerate(plan):
if _name not in self.operators.keys():
self.add(Operator(_name, ""))
if not self.config.free_room:
if self.operators[_name].is_high() and not self.operators[
_name
].room.startswith("dorm"):
ret.append(i)
elif not self.operators[_name].room.startswith("dorm"):
ret.append(i)
break
return ret
def get_dorm_by_name(self, name):
for idx, dorm in enumerate(self.dorm):
if dorm.name == name:
return idx, dorm
return None, None
def add(self, operator):
if operator.name not in list(agent_list.keys()):
return
if self.config.is_resting_priority(operator.name):
operator.resting_priority = "low"
operator.exhaust_require = self.config.is_exhaust_require(operator.name)
operator.rest_in_full = self.config.is_rest_in_full(operator.name)
operator.workaholic = self.config.is_workaholic(operator.name)
operator.refresh_order_room = self.config.is_refresh_trading(operator.name)
if operator.name in agent_arrange_order:
operator.arrange_order = agent_arrange_order[operator.name]
# 复制基建数据
if operator.name in self.shadow_copy:
exist = self.shadow_copy[operator.name]
operator.mood = exist.mood
operator.time_stamp = exist.time_stamp
operator.depletion_rate = exist.depletion_rate
operator.current_room = exist.current_room
operator.current_index = exist.current_index
self.operators[operator.name] = operator
# 需要用尽心情干员逻辑
if (
operator.exhaust_require or operator.group in self.exhaust_group
) and operator.name not in self.exhaust_agent:
self.exhaust_agent.append(operator.name)
if operator.group != "":
self.exhaust_group.append(operator.group)
# 干员分组逻辑
if operator.group != "":
if operator.group not in self.groups.keys():
self.groups[operator.group] = [operator.name]
else:
self.groups[operator.group].append(operator.name)
if operator.workaholic and operator.name not in self.workaholic_agent:
self.workaholic_agent.append(operator.name)
def available_free(self, free_type="high"):
ret = 0
freeName = []
if free_type == "high":
idx = 0
for dorm in self.dorm:
if dorm.name == "" or (
dorm.name in self.operators.keys()
and not self.operators[dorm.name].is_high()
):
ret += 1
elif dorm.time is not None and dorm.time < datetime.now():
logger.info(f"检测到房间休息完毕,释放{dorm.name}宿舍位")
freeName.append(dorm.name)
ret += 1
if idx == self.config.max_resting_count - 1:
break
else:
idx += 1
else:
idx = self.config.max_resting_count
for i in range(idx, len(self.dorm)):
dorm = self.dorm[i]
# 释放满休息位
# TODO 高效组且低优先可以相互替换
if dorm.name == "" or (
dorm.name in self.operators.keys()
and not self.operators[dorm.name].is_high()
):
ret += 1
elif dorm.time is not None and dorm.time < datetime.now():
logger.info(f"检测到房间休息完毕,释放{dorm.name}宿舍位")
freeName.append(dorm.name)
ret += 1
if len(freeName) > 0:
for name in freeName:
if name in list(agent_list.keys()):
self.operators[name].mood = self.operators[name].upper_limit
self.operators[name].depletion_rate = 0
self.operators[name].time_stamp = datetime.now()
return ret
def assign_dorm(self, name):
is_high = self.operators[name].resting_priority == "high"
if is_high:
_room = next(
obj
for obj in self.dorm
if obj.name not in self.operators.keys()
or not self.operators[obj.name].is_high()
)
else:
_room = None
for i in range(self.config.max_resting_count, len(self.dorm)):
_name = self.dorm[i].name
if _name == "" or not self.operators[_name].is_high():
_room = self.dorm[i]
break
_room.name = name
return _room
def get_current_operator(self, room, index):
for key, value in self.operators.items():
if value.current_room == room and value.current_index == index:
return value
return None
def print(self):
ret = "{"
op = []
dorm = []
for k, v in self.operators.items():
op.append("'" + k + "': " + str(vars(v)))
ret += "'operators': {" + ",".join(op) + "},"
for v in self.dorm:
dorm.append(str(vars(v)))
ret += "'dorms': [" + ",".join(dorm) + "]}"
return ret
class Dormitory:
def __init__(self, position, name="", time=None):
self.position = position
self.name = name
self.time = time
def __repr__(self):
return (
f"Dormitory(position={self.position},name='{self.name}',time='{self.time}')"
)
class Operator:
time_stamp = None
depletion_rate = 0
workaholic = False
arrange_order = [2, "false"]
def __init__(
self,
name,
room,
index=-1,
group="",
replacement=[],
resting_priority="low",
current_room="",
exhaust_require=False,
mood=24,
upper_limit=24,
rest_in_full=False,
current_index=-1,
lower_limit=0,
operator_type="low",
depletion_rate=0,
time_stamp=None,
refresh_order_room=None,
):
if refresh_order_room is not None:
self.refresh_order_room = refresh_order_room
self.refresh_order_room = [False, []]
self.name = name
self.room = room
self.operator_type = operator_type
self.index = index
self.group = group
self.replacement = replacement
self.resting_priority = resting_priority
self._current_room = None
self.current_room = current_room
self.exhaust_require = exhaust_require
self.upper_limit = upper_limit
self.rest_in_full = rest_in_full
self.mood = mood
self.current_index = current_index
self.lower_limit = lower_limit
self.depletion_rate = depletion_rate
self.time_stamp = time_stamp
@property
def current_room(self):
return self._current_room
@current_room.setter
def current_room(self, value):
if self._current_room != value:
self._current_room = value
if Operators.current_room_changed_callback and self.refresh_order_room[0]:
Operators.current_room_changed_callback(self)
def is_high(self):
return self.operator_type == "high"
def is_resting(self):
return self.current_room.startswith("dorm")
def is_working(self):
return self.current_room in base_room_list and not self.is_resting()
def need_to_refresh(self, h=2, r=""):
# 是否需要读取心情
if (
self.time_stamp is None
or (
self.time_stamp is not None
and self.time_stamp + timedelta(hours=h) < datetime.now()
)
or (r.startswith("dorm") and not self.room.startswith("dorm"))
):
return True
def not_valid(self):
if self.room == "train":
return False
if self.operator_type == "high":
if self.workaholic:
return (
self.current_room != self.room or self.index != self.current_index
)
if not self.room.startswith("dorm") and self.current_room.startswith(
"dorm"
):
if self.mood == -1 or self.mood == 24:
return True
else:
return False
return (
self.need_to_refresh(2.5)
or self.current_room != self.room
or self.index != self.current_index
)
return False
def current_mood(self):
predict = self.mood
if self.time_stamp is not None:
predict = (
self.mood
- self.depletion_rate
* (datetime.now() - self.time_stamp).total_seconds()
/ 3600
)
if 0 <= predict <= 24:
return predict
else:
return self.mood
def __repr__(self):
return f"Operator(name='{self.name}', room='{self.room}', index={self.index}, group='{self.group}', replacement={self.replacement}, resting_priority='{self.resting_priority}', current_room='{self.current_room}',exhaust_require={self.exhaust_require},mood={self.mood}, upper_limit={self.upper_limit}, rest_in_full={self.rest_in_full}, current_index={self.current_index}, lower_limit={self.lower_limit}, operator_type='{self.operator_type}',depletion_rate={self.depletion_rate},time_stamp='{self.time_stamp}',refresh_order_room = {self.refresh_order_room})"