66 lines
4.4 KiB
C#
66 lines
4.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
namespace HotUpdate.Gameplay
|
|
{
|
|
[Serializable] public sealed class CombatUnit
|
|
{
|
|
public int id, team, art, lane, z, hp, maxHp, shield, tier, slot, slow;
|
|
public bool exited, spawned, boss;
|
|
}
|
|
[Serializable] public sealed class CombatTraceManifest
|
|
{
|
|
public int schema, protocol, logicVersion, rulesVersion, tickMs, lastTick, eventCount, initialDurability, durability, defeated;
|
|
public string kind, traceId, traceHash, gameplayHash, skillsHash;
|
|
public float duration;
|
|
public bool won;
|
|
public CombatUnit[] initial, final;
|
|
public int[] leaks;
|
|
}
|
|
[Serializable] public sealed class CombatEvent
|
|
{
|
|
public string kind, skill, action, reason;
|
|
public int seq, tick, source, target, cast, release, end, arrival, fromZ, toZ;
|
|
public int id, team, art, lane, z, hp, maxHp, shield, tier, slot, slow, amount, absorbed, expires, factor, durability, defeated, leaks;
|
|
public bool boss, spawned, exited, won;
|
|
}
|
|
[Serializable] public sealed class CombatPage { public string activityId, traceId, payload, sha256; public int cursor, nextCursor, total; }
|
|
[Serializable] public sealed class CombatPageReply { public bool ok; public string error; public CombatPage page; }
|
|
[Serializable] public sealed class CombatPageCommand
|
|
{
|
|
public string requestId, action="battle.events", activityId, traceId;
|
|
public int revision, cursor, limit=256;
|
|
}
|
|
public static class CombatTraceValidation
|
|
{
|
|
public static string Hash(string text) {using(var hash=SHA256.Create())return BitConverter.ToString(hash.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-","").ToLowerInvariant();}
|
|
static bool HashId(string value) => value!=null&&value.Length==64&&value.All(c=>c>='0'&&c<='9'||c>='a'&&c<='f');
|
|
public static bool Manifest(CombatTraceManifest m)
|
|
{
|
|
if(m==null||m.schema!=1||m.protocol!=1||m.logicVersion!=1||m.rulesVersion!=3||m.kind!="authoritative_battle"||m.tickMs!=50||m.lastTick<0||m.lastTick>=2400||m.eventCount<1||m.eventCount>50000||m.initialDurability<1||m.initialDurability>1000||!HashId(m.traceId)||!HashId(m.traceHash)||!HashId(m.gameplayHash)||!HashId(m.skillsHash)||m.initial==null||m.final?.Length!=m.initial.Length||m.initial.Length<10||m.initial.Length>15)return false;
|
|
var ids=new HashSet<int>();
|
|
foreach(var u in m.initial)
|
|
if(u==null||!ids.Add(u.id)||u.maxHp<1||u.hp<0||u.hp>u.maxHp||u.lane<0||u.lane>2||u.z< -4000||u.z>11000||(u.team==0?(u.id!=u.art||u.art<0||u.art>9||u.slot<0||u.slot>5||u.tier<1||u.tier>2):(u.team!=1||u.id<100||u.id>108||u.art<0||u.art>4)))return false;
|
|
return ids.Count(id=>id>=100)==9&&m.final.All(u=>u!=null&&ids.Contains(u.id))&&m.final.Select(u=>u.id).Distinct().Count()==ids.Count;
|
|
}
|
|
public static bool Events(CombatTraceManifest m,CombatEvent[] events)
|
|
{
|
|
if(!Manifest(m)||events==null||events.Length!=m.eventCount)return false;
|
|
var ids=new HashSet<int>(m.initial.Select(u=>u.id));int last=-1;
|
|
var kinds=new HashSet<string>{"spawn","move","cast_start","release","cast_end","cancel","projectile","hit","heal","shield","slow","shield_expired","slow_expired","death","exit","expired","battle_end"};
|
|
for(int i=0;i<events.Length;i++)
|
|
{
|
|
var e=events[i];if(e==null||e.seq!=i||e.tick<last||e.tick>m.lastTick||!kinds.Contains(e.kind))return false;last=e.tick;
|
|
bool source=e.kind=="cast_start"||e.kind=="release"||e.kind=="cast_end"||e.kind=="cancel"||e.kind=="projectile"||e.kind=="hit"||e.kind=="heal"||e.kind=="shield"||e.kind=="slow"||e.kind=="death"||e.kind=="expired";
|
|
bool target=e.kind=="spawn"||e.kind=="move"||e.kind=="projectile"||e.kind=="hit"||e.kind=="heal"||e.kind=="shield"||e.kind=="slow"||e.kind=="shield_expired"||e.kind=="slow_expired"||e.kind=="death"||e.kind=="exit"||e.kind=="expired";
|
|
if(source&&!ids.Contains(e.source)||target&&!ids.Contains(e.target)||e.kind=="cast_start"&&(e.release<e.tick||e.end<=e.release)||e.kind=="projectile"&&e.arrival<=e.tick)return false;
|
|
}
|
|
return events[events.Length-1].kind=="battle_end"&&events[events.Length-1].tick==m.lastTick;
|
|
}
|
|
}
|
|
}
|