Enhanced multihit config.

This commit is contained in:
genxium
2023-01-01 22:51:46 +08:00
parent 325dbfb79c
commit 4c64c1984c
45 changed files with 5542 additions and 1195 deletions

View File

@@ -28,8 +28,8 @@ const (
SNAP_INTO_PLATFORM_OVERLAP = float64(0.1)
SNAP_INTO_PLATFORM_THRESHOLD = float64(0.5)
NO_SKILL = int32(-1)
NO_SKILL_HIT = int32(-1)
NO_SKILL = -1
NO_SKILL_HIT = -1
)
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
@@ -383,30 +383,9 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
}
}
/*
As long as the current "CharacterState" is not in "noOpSet", we have 2 cases where attacking is allowed:
1. it happens when the player is IDLE or Walking, i.e. "0 == currPlayerDownsync.FramesToRecover", which is just the normal case
2. it happens when the player is having non-empty "ActiveSkillId" which might be cancellable
*/
patternId := PATTERN_ID_NO_OP
if decodedInput.BtnALevel > prevBtnALevel {
if currPlayerDownsync.InAir {
patternId = 255
} else {
patternId = 1
}
}
if PATTERN_ID_NO_OP != patternId && 0 < currPlayerDownsync.FramesToRecover {
// [WARN] Handle skill cancellation
patternId = PATTERN_ID_NO_OP // First, reset the patternId to no-op as it should be by default not cancelling anything
skillConfig := skills[int(currPlayerDownsync.ActiveSkillId)]
switch v := skillConfig.Hits[currPlayerDownsync.ActiveSkillHit].(type) {
case *MeleeBullet:
if v.CancellableStFrame <= currPlayerDownsync.FramesInChState && currPlayerDownsync.FramesInChState < v.CancellableEdFrame {
patternId = int(currPlayerDownsync.ActiveSkillId + 1) // if "currPlayerDownsync.InAir", it won't map to a valid skill in the next step and thus act as PATTERN_ID_NO_OP
}
}
patternId = 1
}
return patternId, jumpedOrNot, effDx, effDy
@@ -463,38 +442,28 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
jumpedOrNotList[i] = true
}
joinIndex := currPlayerDownsync.JoinIndex
if PATTERN_ID_NO_OP != patternId {
if skillId, existent := chConfig.PatternIdToSkillId[patternId]; existent {
skillConfig := skills[skillId]
thatPlayerInNextFrame.ActiveSkillId = int32(skillId)
thatPlayerInNextFrame.ActiveSkillHit = 0
skillId := chConfig.SkillMapper(patternId, currPlayerDownsync)
if skillConfig, existent := skills[skillId]; existent {
thatPlayerInNextFrame.ActiveSkillId = int32(skillId)
thatPlayerInNextFrame.ActiveSkillHit = 0
// TODO: Respect non-zero "selfLockVel"
// TODO: Respect non-zero "selfLockVel"
// Hardcoded to use only the first hit for now
switch v := skillConfig.Hits[thatPlayerInNextFrame.ActiveSkillHit].(type) {
case *MeleeBullet:
var newBullet MeleeBullet = *v // Copied primitive fields into an onstack variable
newBullet.OriginatedRenderFrameId = currRenderFrame.Id
newBullet.OffenderJoinIndex = joinIndex
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newBullet)
thatPlayerInNextFrame.FramesToRecover = skillConfig.RecoveryFrames
}
// TODO: How to differentiate skill cancellable among different characters, e.g. some characters might be allowed to have 4-cancellation-combo or more?
switch skillId {
case 1:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
case 2:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK2
case 3:
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK3
}
if false == currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX = 0
}
continue // Don't allow movement if skill is used
// Hardcoded to use only the first hit for now
switch v := skillConfig.Hits[thatPlayerInNextFrame.ActiveSkillHit].(type) {
case *MeleeBullet:
var newBullet MeleeBullet = *v // Copied primitive fields into an onstack variable
newBullet.OriginatedRenderFrameId = currRenderFrame.Id
newBullet.OffenderJoinIndex = joinIndex
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newBullet)
thatPlayerInNextFrame.FramesToRecover = skillConfig.RecoveryFrames
}
thatPlayerInNextFrame.CharacterState = skillConfig.BoundChState
if false == currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX = 0
}
continue // Don't allow movement if skill is used
}
if 0 == currPlayerDownsync.FramesToRecover {
@@ -720,8 +689,8 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// Remove any active skill if not attacking
if _, existent := nonAttackingSet[thatPlayerInNextFrame.CharacterState]; existent {
thatPlayerInNextFrame.ActiveSkillId = NO_SKILL
thatPlayerInNextFrame.ActiveSkillHit = NO_SKILL_HIT
thatPlayerInNextFrame.ActiveSkillId = int32(NO_SKILL)
thatPlayerInNextFrame.ActiveSkillHit = int32(NO_SKILL_HIT)
}
}

View File

@@ -1,5 +1,7 @@
package battle
type SkillMapperType func(patternId int, currPlayerDownsync *PlayerDownsync) int
type CharacterConfig struct {
SpeciesId int
SpeciesName string
@@ -15,7 +17,7 @@ type CharacterConfig struct {
JumpingInitVelY int32
PatternIdToSkillId map[int]int
SkillMapper SkillMapperType
}
var Characters = map[int]*CharacterConfig{
@@ -34,26 +36,89 @@ var Characters = map[int]*CharacterConfig{
JumpingInitVelY: int32(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO),
PatternIdToSkillId: map[int]int{
1: 1, // Atk1
2: 2, // Atk2
3: 3, // Atk3
255: 255, // InAirAtk1
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
if 1 == patternId {
if 0 == currPlayerDownsync.FramesToRecover {
if currPlayerDownsync.InAir {
return 255
} else {
return 1
}
} else {
// Now that "0 < FramesToRecover", we're only able to fire any skill if it's a cancellation
if skillConfig, existent1 := skills[int(currPlayerDownsync.ActiveSkillId)]; existent1 {
switch v := skillConfig.Hits[currPlayerDownsync.ActiveSkillHit].(type) {
case *MeleeBullet:
if v.CancellableStFrame <= currPlayerDownsync.FramesInChState && currPlayerDownsync.FramesInChState < v.CancellableEdFrame {
if nextSkillId, existent2 := v.CancelTransit[patternId]; existent2 {
return nextSkillId
}
}
}
}
}
}
// By default no skill can be fired
return NO_SKILL
},
},
1: &CharacterConfig{
SpeciesId: 1,
SpeciesName: "KnifeGirl",
InAirIdleFrameIdxTurningPoint: 9,
InAirIdleFrameIdxTurnedCycle: 1,
LayDownFrames: int32(16),
LayDownFramesToRecover: int32(16),
GetUpFrames: int32(30),
GetUpFramesToRecover: int32(27), // 3 invinsible frames for just-blown-up character to make a comeback
JumpingInitVelY: int32(float64(7.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
SkillMapper: func(patternId int, currPlayerDownsync *PlayerDownsync) int {
if 1 == patternId {
if 0 == currPlayerDownsync.FramesToRecover {
if currPlayerDownsync.InAir {
return 256
} else {
return 4
}
} else {
// Now that "0 < FramesToRecover", we're only able to fire any skill if it's a cancellation
if skillConfig, existent1 := skills[int(currPlayerDownsync.ActiveSkillId)]; existent1 {
switch v := skillConfig.Hits[currPlayerDownsync.ActiveSkillHit].(type) {
case *MeleeBullet:
if v.CancellableStFrame <= currPlayerDownsync.FramesInChState && currPlayerDownsync.FramesInChState < v.CancellableEdFrame {
if nextSkillId, existent2 := v.CancelTransit[patternId]; existent2 {
return nextSkillId
}
}
}
}
}
}
// By default no skill can be fired
return NO_SKILL
},
},
}
var skills = map[int]*Skill{
1: &Skill{
RecoveryFrames: int32(20),
RecoveryFramesOnBlock: int32(20),
RecoveryFramesOnHit: int32(20),
RecoveryFrames: int32(30),
RecoveryFramesOnBlock: int32(30),
RecoveryFramesOnHit: int32(30),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(5),
ActiveFrames: int32(10),
StartupFrames: int32(7),
ActiveFrames: int32(22),
HitStunFrames: int32(13),
BlockStunFrames: int32(9),
Damage: int32(5),
@@ -63,8 +128,12 @@ var skills = map[int]*Skill{
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(8),
CancellableEdFrame: int32(20),
CancellableStFrame: int32(13),
CancellableEdFrame: int32(30),
CancelTransit: map[int]int{
1: 2,
},
// TODO: Use non-zero "selfLockVel"
},
},
@@ -75,11 +144,12 @@ var skills = map[int]*Skill{
RecoveryFramesOnBlock: int32(36),
RecoveryFramesOnHit: int32(36),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK2,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(3),
ActiveFrames: int32(20),
StartupFrames: int32(18),
ActiveFrames: int32(18),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Damage: int32(5),
@@ -89,8 +159,11 @@ var skills = map[int]*Skill{
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(18),
CancellableStFrame: int32(22),
CancellableEdFrame: int32(36),
CancelTransit: map[int]int{
1: 3,
},
},
},
},
@@ -100,16 +173,102 @@ var skills = map[int]*Skill{
RecoveryFramesOnBlock: int32(60),
RecoveryFramesOnHit: int32(60),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK3,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(1),
ActiveFrames: int32(30),
StartupFrames: int32(15),
ActiveFrames: int32(40),
HitStunFrames: MAX_INT32,
BlockStunFrames: int32(9),
Damage: int32(10),
PushbackVelX: int32(float64(1) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(float64(4) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelX: int32(float64(2) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
BlowUp: true,
},
},
},
},
4: &Skill{
RecoveryFrames: int32(30),
RecoveryFramesOnBlock: int32(30),
RecoveryFramesOnHit: int32(30),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(7),
ActiveFrames: int32(22),
HitStunFrames: int32(13),
BlockStunFrames: int32(9),
Damage: int32(5),
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(13),
CancellableEdFrame: int32(30),
CancelTransit: map[int]int{
1: 5,
},
// TODO: Use non-zero "selfLockVel"
},
},
},
},
5: &Skill{
RecoveryFrames: int32(36),
RecoveryFramesOnBlock: int32(36),
RecoveryFramesOnHit: int32(36),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK2,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(18),
ActiveFrames: int32(18),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Damage: int32(5),
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(18) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
CancellableStFrame: int32(22),
CancellableEdFrame: int32(36),
CancelTransit: map[int]int{
1: 6,
},
},
},
},
},
6: &Skill{
RecoveryFrames: int32(60),
RecoveryFramesOnBlock: int32(60),
RecoveryFramesOnHit: int32(60),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_ATK3,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(15),
ActiveFrames: int32(40),
HitStunFrames: MAX_INT32,
BlockStunFrames: int32(9),
Damage: int32(10),
PushbackVelX: int32(float64(2) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(float64(7) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
@@ -124,6 +283,31 @@ var skills = map[int]*Skill{
RecoveryFramesOnBlock: int32(34),
RecoveryFramesOnHit: int32(34),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{
StartupFrames: int32(3),
ActiveFrames: int32(20),
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Damage: int32(5),
PushbackVelX: int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO),
PushbackVelY: int32(0),
HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeY: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
},
},
},
},
256: &Skill{
RecoveryFrames: int32(34),
RecoveryFramesOnBlock: int32(34),
RecoveryFramesOnHit: int32(34),
ReleaseTriggerType: int32(1),
BoundChState: ATK_CHARACTER_STATE_INAIR_ATK1,
Hits: []interface{}{
&MeleeBullet{
Bullet: Bullet{

View File

@@ -79,6 +79,8 @@ type Bullet struct {
HitboxSizeY int32
BlowUp bool
CancelTransit map[int]int
}
type MeleeBullet struct {
@@ -101,7 +103,8 @@ type Skill struct {
RecoveryFrames int32
RecoveryFramesOnBlock int32
RecoveryFramesOnHit int32
ReleaseTriggerType int32 // 1: rising-edge, 2: falling-edge
ReleaseTriggerType int32 // 1: rising-edge, 2: falling-edge
BoundChState int32
Hits []interface{} // Hits within a "Skill" are automatically triggered
}