using System.Collections.Generic; using System.Linq; using Superlazy; using UnityEngine; public static class UnitUtil { // Unit public static SLEntity MakeUnitBase(string unitBind, string eventID, string unitType) { var unit = SLSystem.Data["Units"][unitBind].Override(); unit["EventID"] = eventID; unit["UnitType"] = unitType; unit.AddComponent("MoveController"); unit.AddComponent("ActionController"); return unit; } public static void AddComponent(this SLEntity unitBase) where T : UnitComponent { AddComponent(unitBase, typeof(T).Name, typeof(T).Name, SLEntity.Empty); } public static void AddComponent(this SLEntity unitBase, string key, SLEntity context) where T : UnitComponent { AddComponent(unitBase, key, typeof(T).Name, context); } public static void AddComponent(this SLEntity unitBase, string component) { AddComponent(unitBase, component, component, SLEntity.Empty); } public static void AddComponent(this SLEntity unitBase, string key, string component, SLEntity context) { unitBase["Components"][key] = context; unitBase["Components"][key]["Component"] = component; } // Battle public static void MakeBattleMessageContext(this SLEntity context, IUnit unit, SLEntity skill) { MakeBattleMessageContext(context, unit); if (context["SkillType"] == false) { context["SkillType"] = skill["SkillType"]; } if (context["UsingType"] == false) { context["UsingType"] = skill["UsingType"]; } if (context["Target"] == false) { context["Target"] = skill["Target"]; } if (skill["Damage"]) { context["Damage"] = skill["Damage"]; context["Attack"] = unit.Entity["Attack"]; if (skill["SkillType"] == "NormalAttack") { context["Damage"] *= 1 + unit.Entity["NormalAttackDamage"]; } else if (skill["SkillType"] == "Skill") { context["Damage"] *= 1 + unit.Entity["SkillDamage"]; } if (skill["UsingType"] == "MultiAttack") { context["Damage"] *= 1 + unit.Entity["MultiAttackDamage"]; } else if (skill["UsingType"] == "CounterAttack") { context["Damage"] *= 1 + unit.Entity["CounterAttackDamage"]; } if (context["Projectile"]) //TODO: 가짜 프로젝타일 처리 { context["Damage"] *= 1 + unit.Entity["ProjectileDamage"]; } else { context["Damage"] *= 1 + unit.Entity["ImpactDamage"]; } context["CriticalRatio"] = unit.Entity["CriticalRatio"]; context["CriticalDamage"] = unit.Entity["CriticalDamage"] + SLSystem.Data["Default"]["CriticalDamageBase"]; if (context["ActionStop"] == false) { context["ActionStop"] = 10; } if (context["HitStop"] == false) { context["HitStop"] = 10; } } if (skill["Shield"]) { context["Shield"] = Mathf.CeilToInt(skill["Shield"] * unit.Entity["MaxHP"]); } if (skill["Heal"]) { context["Heal"] = Mathf.CeilToInt(skill["Heal"] * unit.Entity["Attack"] * (1 + unit.Entity["HealBoost"])); } if (skill["HPHeal"]) { context["Heal"] += Mathf.CeilToInt(skill["HPHeal"] * unit.Entity["MaxHP"] * (1 + unit.Entity["HealBoost"])); } // 공통 if (skill["Buff"]) { context["TargetBuffs"][skill["Buff"]].MakeBuffContext(skill["Buff"], unit, skill); } foreach (var hitIDBuff in skill["HitIDBuffs"]) { if (hitIDBuff.ID != context["HitID"].ToString()) continue; context["TargetBuffs"][hitIDBuff.ID].MakeBuffContext(hitIDBuff["Buff"], unit, hitIDBuff); // TODO: 타겟버프 늘어나는 케이스 체크 AddedTargetBuffs 처럼 } //foreach (var targetBuff in skill["AddedTargetBuffs"]) //{ // context["TargetBuffs"]["Added" + targetBuff.ID].MakeBuffContext(unit, targetBuff["Buff"], targetBuff); //} } // Projectile등 컨텍스트만 업데이트 public static void MakeBattleMessageContext(this SLEntity context, IUnit unit) { // 공격하는 유닛이 죽을 경우를 대비해 Context에 나눠서 처리 if (context["Owner"] == false) // 최초대입때 Owner세팅 { context["Owner"]["Handle"] = unit.Entity["Handle"]; context["Owner"]["Position"] = unit.Entity["Position"]; context["Owner"]["Forward"] = unit.Entity["Forward"]; context["Owner"]["UnitType"] = unit.Entity["UnitType"]; } context["Sender"]["Handle"] = unit.Entity["Handle"]; context["Sender"]["Position"] = unit.Entity["Position"]; context["Sender"]["Forward"] = unit.Entity["Forward"]; context["Sender"]["UnitType"] = unit.Entity["UnitType"]; } public static void SendBattleMessage(IZone zone, SLEntity targetContext, SLEntity context) { IUnit mainTarget = null; if (targetContext["Handle"]) { mainTarget = zone.GetUnit(targetContext["Handle"]); } var area = 0.3f; // TODO: 스플래시 등 처리 var targetPosition = targetContext["Position"].ToVector3(); // 범위 내에서 거리 이내에 있는 var targets = zone.Units.Where(u => CheckTarget(context["Owner"], context, u.Entity) && u.Entity["Radius"] + area >= Vector3.Distance(targetPosition, u.Entity["Position"].ToVector3())); if (targets.Any()) { var target = targets.MaxBy(u => (u.Entity["Handle"] == targetContext["Handle"] ? 100 : 0) + (50 - u.Entity["Radius"] - Vector3.Distance(targetPosition, u.Entity["Position"].ToVector3()))); target.Message(context["Message"], context); } } public static bool CheckTarget(SLEntity currentUnit, SLEntity currentSkill, SLEntity targetUnit) { if (targetUnit["UnitType"] == false) return false; // 투사체? TODO if (currentSkill["Target"] == "Self" && targetUnit["Handle"] != currentUnit["Handle"]) return false; if (currentSkill["Target"] == "Enemy" && ((currentUnit["UnitType"] == "Ally" && targetUnit["UnitType"] == "Party") || (currentUnit["UnitType"] == "Party" && targetUnit["UnitType"] == "Ally"))) return false; if (currentSkill["Target"] == "Enemy" && (targetUnit["UnitType"] == currentUnit["UnitType"] || targetUnit["CantTarget"] || targetUnit["CantTargetEnemy"] || (targetUnit["NoMove"] && targetUnit["MaxHP"] == false) /*|| targetUnit["HP"] == 0*/)) return false; if (currentSkill["Target"] == "Ally" && (targetUnit["UnitType"] != currentUnit["UnitType"] || targetUnit["CantTarget"] || (targetUnit["NoMove"] && targetUnit["MaxHP"] == false) /*|| targetUnit["HP"] == 0*/)) return false; return true; } public static int AddHP(this Unit unit, int addHP) { if (unit.Entity["MaxHP"] == false) return 0; if (unit.Entity["HP"] == 0) return 0; if (addHP < 0 && unit.Entity["Shield"] > 0) { unit.Entity["Shield"] += addHP; if (unit.Entity["Shield"] < 0) { addHP = unit.Entity["Shield"]; unit.Entity["Shield"] = 0; } else { addHP = 0; } } var originHP = unit.Entity["HP"]; unit.Entity["HP"] += addHP; if (unit.Entity["HP"] > unit.Entity["MaxHP"]) { unit.Entity["HP"] = unit.Entity["MaxHP"]; } if (unit.Entity["HP"] <= 0) { unit.Entity["HP"] = 0; } var changedHP = unit.Entity["HP"] - originHP; return changedHP; } public static void AddShield(this Unit unit, int addShield) { unit.Entity["Shield"] += addShield; } public static int BattleRandom(IZone zone, int min, int max) { var result = new System.Random(zone.Entity["BattleSeed"]).Next(min, max + 1); zone.Entity["BattleSeed"] = new System.Random(zone.Entity["BattleSeed"]).Next(); return result; } public static double BattleRandom(IZone zone) { var result = new System.Random(zone.Entity["BattleSeed"]).NextDouble(); zone.Entity["BattleSeed"] = new System.Random(zone.Entity["BattleSeed"]).Next(); return result; } // Buff public static void MakeBuffContext(this SLEntity context, string buff, IUnit unit, SLEntity skill) { var commonBuff = SLSystem.Data["Buffs"][buff]; if (commonBuff == false) { SLLog.Error($"[{buff}] 없음. Buffs 에서 만들어주세요"); return; } context["BuffKey"] = buff; context["BuffEffect"] = buff; context["Name"] = commonBuff["Name"]; context["Desc"] = commonBuff["Desc"]; if (commonBuff["Component"]) { context["BuffName"] = commonBuff["Component"]; } else { context["BuffName"] = "BattleBuff"; } context["BuffType"] = commonBuff["BuffType"]; context["LookCaster"] = commonBuff["LookCaster"]; context["CantAction"] = commonBuff["CantAction"]; context["UseMove"] = commonBuff["UseMove"]; context["Owner"]["Handle"] = unit.Entity["Handle"]; context["Owner"]["UnitType"] = unit.Entity["UnitType"]; UnitHandler.MakeContext("MakeBuffContext", buff, context, unit, skill); } public static void BuffBegin(Unit unit, SLEntity buff) { if (buff["CantAction"]) { unit.Entity["CantAction"][buff.ID] = true; unit.Message("CancelSkill", SLEntity.Empty); } if (buff["LookCaster"]) { var diff = buff["Position"].ToVector3() - unit.Entity["Position"].ToVector3(); unit.Entity["Forward"] = diff.ToEntity(); } var statContext = SLEntity.Empty; foreach (var stat in SLSystem.Data["Stats"]) { if (buff[stat.ID] == false) continue; // End와 대칭맞추기용 statContext[stat.ID] = buff[stat.ID]; } if (statContext) { unit.Message("AddStat", statContext); } } public static void BuffUpdate(Unit unit, SLEntity buff) { if (buff["Duration"]) { buff["Duration"] -= 1; if (buff["Duration"] <= 0) { buff["End"] = true; } } } public static void BuffEnd(Unit unit, SLEntity buff) { if (buff["CantAction"]) { unit.Entity["CantAction"][buff.ID] = false; } var statContext = SLEntity.Empty; foreach (var stat in SLSystem.Data["Stats"]) { if (buff[stat.ID] == false) continue; // End와 대칭맞추기용 statContext[stat.ID] = -buff[stat.ID]; } if (statContext) { unit.Message("AddStat", statContext); } } // action // 이건 액션컨트롤러 스태틱으로? public static bool CanAction(this Unit unit) { if (unit.Entity["To"]) return false; // 이동, 넉백 등 if (unit.Entity["CantAction"]) return false; // 스턴, 사망 등 return true; } public static void SetActionIdle(this Unit unit) { if (unit.CheckActionEnd() && unit.Entity["Action"] == false) { unit.SetAction("Idle"); } } public static void SetAction(this Unit unit, string action) { unit.Entity["Action"] = action; } public static bool CheckActionEnd(this Unit unit, string frameType = null) { if (unit.Entity.HasChild("Action")) return false; // change Action var currentAction = unit.Entity["CurrentAction"]; if (currentAction.HasChild("Frame") == false) return true; if (currentAction.HasChild("Loop")) return true; var actionEndFrame = currentAction["Frame"]; if (frameType != null && currentAction.HasChild(frameType)) { actionEndFrame = currentAction[frameType]; } return currentAction["CurrentFrame"] >= actionEndFrame; } public static SLEntity GetAction(this SLEntity unit, string action) { // 기본액션들이 있을 때 if (unit["Actions"][action]) return SLSystem.Data["Actions"][unit["Actions"][action]]; if (SLSystem.Data["AlternativeActions"][action]) { // 일반액션 폴백 먼저하고 후속으로 커먼액션 폴백 foreach (var alternativeAction in SLSystem.Data["AlternativeActions"][action]) { if (unit["Actions"][alternativeAction]) { return SLSystem.Data["Actions"][unit["Actions"][alternativeAction]]; } } } // 일반액션 폴백 먼저하고 후속으로 커먼액션 폴백 if (unit["CommonAction"]) { if (SLSystem.Data["CommonActions"][action]) return SLSystem.Data["Actions"][SLSystem.Data["CommonActions"][action]]; } if (SLSystem.Data["AlternativeActions"][action]) { foreach (var alternativeAction in SLSystem.Data["AlternativeActions"][action]) { if (unit["CommonAction"] && SLSystem.Data["CommonActions"][alternativeAction]) { return SLSystem.Data["Actions"][SLSystem.Data["CommonActions"][alternativeAction]]; } } } SLLog.Error($"[{unit["UnitType"]}] {action} 없음. Actions 에서 만들어주세요"); return SLEntity.Empty; } // Zone public static IEnumerable GetEventTargets(this Zone zone, SLEntity targets) { var party = zone.Player["Party"]; // TODO: Party 체크 글로벌로 하거나 구조 동일하게 항상 유지가 불가능할것같아서 빼거나 등 필요 foreach (var target in targets) { if (target.ID == "NPC") { var id = SLGame.Session["LastTarget"]["EventID"]; // TODO: LastTarget도 애매하다 if (zone.Entity["EventIDs"][id] == false) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][id]); } else if (target.ID == "PartyAll") { var partyUnits = party.OrderBy(p => p); foreach (var partyUnit in partyUnits) { if (zone.Entity["EventIDs"][partyUnit.ID] == false) continue; if (targets.Any(t => t.ID == partyUnit.ID)) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][partyUnit.ID]); } } else if (target.ID == "Party1") { if (party.Count() < 1) continue; var id = party.OrderBy(p => p["Order"]).ElementAt(0).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][id]); } else if (target.ID == "Party2") { if (party.Count() < 2) continue; var id = party.OrderBy(p => p["Order"]).ElementAt(1).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][id]); } else if (target.ID == "Party3") { if (party.Count() < 3) continue; var id = party.OrderBy(p => p["Order"]).ElementAt(2).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][id]); } else if (target.ID == "Party4") { if (party.Count() < 4) continue; var id = party.OrderBy(p => p["Order"]).ElementAt(3).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][id]); } else if (target.ID == "Party5") { if (party.Count() < 5) continue; var id = party.OrderBy(p => p["Order"]).ElementAt(4).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][id]); } else { if (zone.Entity["EventIDs"][target.ID] == false) continue; yield return zone.GetUnit(zone.Entity["EventIDs"][target.ID]); } } } public static void MessageToTargets(this Zone zone, string message, SLEntity targets) { var party = zone.Player["Party"]; // TODO: Party 체크 글로벌로 하거나 구조 동일하게 항상 유지가 불가능할것같아서 빼거나 등 필요 foreach (var target in targets) { if (target.ID == "NPC") { var id = SLGame.Session["LastTarget"]["EventID"]; // TODO: LastTarget도 애매하다 if (zone.Entity["EventIDs"][id] == false) continue; zone.GetUnit(zone.Entity["EventIDs"][id]).Message(message, target); } else if (target.ID == "PartyAll") { var partyUnits = party.Where(e => zone.Units.Any(u => u.Entity["UnitType"] == "Party" && u.Entity["EventID"] == e.ID)).OrderBy(p => SLSystem.Data["Characters"][p.ID]["Index"]); foreach (var partyUnit in partyUnits) { if (zone.Entity["EventIDs"][partyUnit.ID] == false) continue; if (targets.Any(t => t.ID == partyUnit.ID)) continue; zone.GetUnit(zone.Entity["EventIDs"][partyUnit.ID]).Message(message, target); } } else if (target.ID == "Party1") { var fieldParty = party.Where(e => zone.Units.Any(u => u.Entity["UnitType"] == "Party" && u.Entity["EventID"] == e.ID)); if (fieldParty.Count() < 1) continue; var id = fieldParty.OrderBy(p => p["Order"]).ElementAt(0).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; zone.GetUnit(zone.Entity["EventIDs"][id]).Message(message, target); } else if (target.ID == "Party2") { var fieldParty = party.Where(e => zone.Units.Any(u => u.Entity["UnitType"] == "Party" && u.Entity["EventID"] == e.ID)); if (fieldParty.Count() < 2) continue; var id = fieldParty.OrderBy(p => p["Order"]).ElementAt(1).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; zone.GetUnit(zone.Entity["EventIDs"][id]).Message(message, target); } else if (target.ID == "Party3") { var fieldParty = party.Where(e => zone.Units.Any(u => u.Entity["UnitType"] == "Party" && u.Entity["EventID"] == e.ID)); if (fieldParty.Count() < 3) continue; var id = fieldParty.OrderBy(p => p["Order"]).ElementAt(2).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; zone.GetUnit(zone.Entity["EventIDs"][id]).Message(message, target); } else if (target.ID == "Party4") { var fieldParty = party.Where(e => zone.Units.Any(u => u.Entity["UnitType"] == "Party" && u.Entity["EventID"] == e.ID)); if (fieldParty.Count() < 4) continue; var id = fieldParty.OrderBy(p => p["Order"]).ElementAt(3).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; zone.GetUnit(zone.Entity["EventIDs"][id]).Message(message, target); } else if (target.ID == "Party5") { var fieldParty = party.Where(e => zone.Units.Any(u => u.Entity["UnitType"] == "Party" && u.Entity["EventID"] == e.ID)); if (fieldParty.Count() < 5) continue; var id = fieldParty.OrderBy(p => p["Order"]).ElementAt(4).ID; if (zone.Entity["EventIDs"][id] == false) continue; if (targets.Any(t => t.ID == id)) continue; zone.GetUnit(zone.Entity["EventIDs"][id]).Message(message, target); } else { if (zone.Entity["EventIDs"][target.ID] == false) continue; zone.GetUnit(zone.Entity["EventIDs"][target.ID]).Message(message, target); } } } }