syntax = "proto3";
option go_package = "battle_srv/protos"; // here "./" corresponds to the "--go_out" value in "protoc" command 

package protos;
import "geometry.proto"; // The import path here is only w.r.t. the proto file, not the Go package.

message PlayerDownsync {
  int32 id = 1;
  int32 virtualGridX = 2; 
  int32 virtualGridY = 3; 
  int32 dirX = 4;
  int32 dirY = 5; // "dirX" and "dirY" determines character facing  
  int32 velX = 6; 
  int32 velY = 7; // "velX" and "velY" is used to record the accumulated effect by accelerations (including gravity)
  int32 speed = 8; // this is the instantaneous scalar attribute of a character, different from but will be accounted in "velX" and "velY" 
  int32 battleState = 9;
  int32 joinIndex = 10;
  double colliderRadius = 11;
  bool removed = 12;
  int32 score = 13;
  int32 lastMoveGmtMillis = 14;
  int32 framesToRecover = 15;
  int32 hp = 16;
  int32 maxHp = 17;
  int32 characterState = 18;
  bool inAir = 19; // by design a standalone field only inferred by the collision result of "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" instead of "characterState", because we need check the transition for "characterState" from this field, i.e. "inAir (prev -> curr)"  

  string name = 20;
  string displayName = 21;
  string avatar = 22;
}

message InputFrameDecoded {
  int32 dx = 1;
  int32 dy = 2;
  int32 btnALevel = 3;
  int32 btnBLevel = 4;
}

message InputFrameUpsync {
  int32 inputFrameId = 1;
  uint64 encoded = 2;
}

message InputFrameDownsync {
  int32 inputFrameId = 1;
  repeated uint64 inputList = 2; // Indexed by "joinIndex", we try to compress the "single player input" into 1 word (64-bit for 64-bit Golang runtime) because atomic compare-and-swap only works on 1 word. Although CAS on custom struct is possible in Golang 1.19 https://pkg.go.dev/sync/atomic@go1.19.1#Value.CompareAndSwap, using a single word is still faster whenever possible. 
  uint64 confirmedList = 3; // Indexed by "joinIndex", same compression concern as above
}

message HeartbeatUpsync {
  int64 clientTimestamp = 1;     
}

message WsReq {
  int32 msgId = 1;
  int32 playerId = 2;
  int32 act = 3;    
  int32 joinIndex = 4;
  int32 ackingFrameId = 5;
  int32 ackingInputFrameId = 6;
  repeated InputFrameUpsync inputFrameUpsyncBatch = 7; 
  HeartbeatUpsync hb = 8; 
}

message WsResp {
  int32 ret = 1;
  int32 echoedMsgId = 2;    
  int32 act = 3; 
  RoomDownsyncFrame rdf = 4; 
  repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
  BattleColliderInfo bciFrame = 6; 
}

message InputsBufferSnapshot {
  int32 refRenderFrameId = 1;    
  uint64 unconfirmedMask = 2;
  repeated InputFrameDownsync toSendInputFrameDownsyncs = 3;
}

message MeleeBullet { 
  // Jargon reference https://www.thegamer.com/fighting-games-frame-data-explained/
  // ALL lengths are in world coordinate

  // for offender  
  int32 battleLocalId = 1;
  int32 startupFrames = 2; 
  int32 activeFrames = 3;  
  int32 recoveryFrames = 4;
  int32 recoveryFramesOnBlock = 5; 
  int32 recoveryFramesOnHit = 6;  
  sharedprotos.Vec2D moveforward = 7;  
  double hitboxOffset = 8;  
  sharedprotos.Vec2D hitboxSize = 9;    
  int32 originatedRenderFrameId = 10; 

  // for defender
  int32 hitStunFrames = 11; 
  int32 blockStunFrames = 12; 
  double pushback = 13;  

  int32 releaseTriggerType = 14; // 1: rising-edge, 2: falling-edge  
  int32 damage = 15;   

  int32 offenderJoinIndex = 16;    
  int32 offenderPlayerId = 17;    
} 

message BattleColliderInfo {
  string stageName = 1;
  map<string, sharedprotos.Vec2DList> strToVec2DListMap = 2; 
  map<string, sharedprotos.Polygon2DList> strToPolygon2DListMap = 3; 
  int32 stageDiscreteW = 4;
  int32 stageDiscreteH = 5;
  int32 stageTileW = 6;
  int32 stageTileH = 7;

  int32 intervalToPing        = 8;
  int32 willKickIfInactiveFor = 9;
  int32 boundRoomId           = 10;
  int32 battleDurationFrames  = 12;
  int64 battleDurationNanos   = 13;
  int32 serverFps             = 14;
  int32 inputDelayFrames      = 15; // in the count of render frames
  uint32 inputScaleFrames     = 16; // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
  int32 nstDelayFrames        = 17; // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
  int32 inputFrameUpsyncDelayTolerance = 18;  
  int32 maxChasingRenderFramesPerUpdate = 19;
  int32 playerBattleState = 20;
  double rollbackEstimatedDtMillis = 21;
  int64 rollbackEstimatedDtNanos = 22;
 
  double worldToVirtualGridRatio = 23;
  double virtualGridToWorldRatio = 24;

  int32 spAtkLookupFrames = 25;
  int32 renderCacheSize = 26;

  map<int32, MeleeBullet> meleeSkillConfig = 27; // skillId -> skill
}

message RoomDownsyncFrame {
  int32 id = 1;
  map<int32, PlayerDownsync> players = 2;
  int64 countdownNanos = 3;
  repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise 
  uint64 backendUnconfirmedMask = 5; // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync
}
