ProjectDDD/Packages/SLUnity/UnitSytem/UnitUtil.cs
2025-07-08 19:46:31 +09:00

568 lines
21 KiB
C#

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<T>(this SLEntity unitBase) where T : UnitComponent
{
AddComponent(unitBase, typeof(T).Name, typeof(T).Name, SLEntity.Empty);
}
public static void AddComponent<T>(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<IUnit> 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);
}
}
}
}