16 Commits

Author SHA1 Message Date
server
7d078e4df8 issue-10: add talisman ui support 2026-04-16 22:29:08 +02:00
server
12df5a2bd6 issue-9: add sash inventory integration 2026-04-16 21:08:13 +02:00
server
09efcadbdd issue-8: add switchbot ui 2026-04-16 20:38:12 +02:00
server
ecc99a8683 issue-7: add stone queue ui 2026-04-16 20:02:14 +02:00
server
7871288b6d issue-6: add autopickup settings ui 2026-04-16 19:28:16 +02:00
server
4ef859e26f issue-5: add teleport hud 2026-04-16 18:33:07 +02:00
server
c11008d1c8 issue-4: add chat fallback for biolog submit 2026-04-16 17:36:46 +02:00
server
a52debbb2a issue-4: add biolog hud 2026-04-16 17:27:05 +02:00
root
403dddd6af Refresh private shop ad board on render 2026-04-16 10:56:45 +02:00
root
10915467c1 Smooth player gauge anchor on high FPS 2026-04-16 10:48:08 +02:00
root
780900af61 Add render FPS and perf HUD settings 2026-04-16 10:37:13 +02:00
root
b9065ca8d2 Remove orphan effects and add headless smoke hooks 2026-04-14 21:22:50 +02:00
root
8767a2b3ac Add effect texture aliases for runtime validation 2026-04-14 19:27:13 +02:00
root
0edaea5993 Fix orc lord front damage motion reference 2026-04-14 19:22:49 +02:00
root
5e79fc27e1 Fix missing dualhand combo 7 sound reference 2026-04-14 19:10:22 +02:00
root
95fc2df7c0 Add missing audio alias assets for runtime validation 2026-04-14 18:59:07 +02:00
60 changed files with 2148 additions and 1558 deletions

View File

@@ -1,524 +0,0 @@
BoundingSphereRadius 0.000000
BoundingSpherePosition 0.000000 0.000000 0.000000
Group Particle
{
StartTime 0.176000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 10.000000
CycleLoopEnable 1
LoopCount 3
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.080000 0.000000 0.000000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 5.000000
}
List TimeEventEmittingDirectionY
{
0.000000 -50.000000
0.100000 -20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 100.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 10.000000
}
List TimeEventSizeX
{
0.000000 60.000000
}
List TimeEventSizeY
{
0.000000 36.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.737143 1.000000
1.000000 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.072000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 10.000000
CycleLoopEnable 1
LoopCount 3
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.080000 0.000000 0.000000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 15.000000
}
List TimeEventEmittingDirectionY
{
0.000000 -50.000000
0.100000 -20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 50.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 10.000000
}
List TimeEventSizeX
{
0.000000 72.000000
}
List TimeEventSizeY
{
0.000000 48.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.737143 1.000000
1.000000 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 10.000000
CycleLoopEnable 1
LoopCount 3
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.080000 0.000000 0.000000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 50.000000
}
List TimeEventEmittingDirectionY
{
0.000000 -50.000000
0.100000 -20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 100.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 5.000000
0.083429 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 10.000000
}
List TimeEventSizeX
{
0.000000 60.000000
}
List TimeEventSizeY
{
0.000000 36.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.737143 1.000000
1.000000 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
0.216000 "MOVING_TYPE_DIRECT" 0.000000 -20.000000 20.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 30
CycleLength 10.000000
CycleLoopEnable 1
LoopCount 3
EmitterShape 3
EmitterAdvancedType 1
EmittingRadius 80.000000
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.010000 0.010000 0.010000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 0.000000
}
List TimeEventEmittingDirectionY
{
0.000000 0.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 20.000000
}
List TimeEventEmittingVelocity
{
0.000000 1.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 10.000000
}
List TimeEventSizeX
{
0.000000 128.000000
}
List TimeEventSizeY
{
0.000000 128.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 4
RotationSpeed 56.000000
RotationRandomStartingBegin 360
RotationRandomStartingEnd 0
AttachEnable 0
StretchEnable 0
TexAniType 0
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
0.000000 0.000000
0.034286 0.100000
}
List TimeEventScaleX
{
0.000000 0.500000
1.000000 1.000000
}
List TimeEventScaleY
{
0.000000 0.500000
1.000000 1.000000
}
List TimeEventColorRed
{
0.000000 0.819608
}
List TimeEventColorGreen
{
0.000000 0.780392
}
List TimeEventColorBlue
{
0.000000 0.682353
}
List TimeEventAlpha
{
0.000000 1.000000
0.112821 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\monster2\smoke_dirt1.dds"
}
}
}

View File

@@ -1,654 +0,0 @@
BoundingSphereRadius 0.000000
BoundingSpherePosition 0.000000 0.000000 0.000000
Group Particle
{
StartTime 0.176000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 20.000000
CycleLoopEnable 1
LoopCount 2
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.080000 0.000000 0.000000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 5.000000
}
List TimeEventEmittingDirectionY
{
0.000000 50.000000
0.100000 20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 100.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 20.000000
}
List TimeEventSizeX
{
0.000000 60.000000
}
List TimeEventSizeY
{
0.000000 36.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.376923 1.000000
0.500000 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.072000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 20.000000
CycleLoopEnable 1
LoopCount 2
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.080000 0.000000 0.000000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 15.000000
}
List TimeEventEmittingDirectionY
{
0.000000 50.000000
0.100000 20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 50.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 20.000000
}
List TimeEventSizeX
{
0.000000 72.000000
}
List TimeEventSizeY
{
0.000000 48.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.387179 1.000000
0.517949 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.072000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 20.000000
CycleLoopEnable 1
LoopCount 2
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.080000 0.000000 0.000000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 15.000000
}
List TimeEventEmittingDirectionY
{
0.000000 50.000000
0.100000 20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 50.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 20.000000
}
List TimeEventSizeX
{
0.000000 72.000000
}
List TimeEventSizeY
{
0.000000 48.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.405128 1.000000
0.505128 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 1
CycleLength 20.000000
CycleLoopEnable 1
LoopCount 2
EmitterShape 0
EmitterAdvancedType 0
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.100000 0.000000 0.050000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 50.000000
}
List TimeEventEmittingDirectionY
{
0.000000 50.000000
0.100000 20.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 10.000000
0.050000 100.000000
0.100000 1000.000000
}
List TimeEventEmittingVelocity
{
0.000000 5.000000
0.083429 15.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 20.000000
}
List TimeEventSizeX
{
0.000000 60.000000
}
List TimeEventSizeY
{
0.000000 36.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 0
RotationSpeed 0.000000
RotationRandomStartingBegin 0
RotationRandomStartingEnd 0
AttachEnable 1
StretchEnable 0
TexAniType 1
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
}
List TimeEventScaleX
{
0.000000 1.000000
}
List TimeEventScaleY
{
0.000000 1.000000
}
List TimeEventColorRed
{
0.000000 1.000000
}
List TimeEventColorGreen
{
0.000000 1.000000
}
List TimeEventColorBlue
{
0.000000 1.000000
}
List TimeEventAlpha
{
0.000000 1.000000
0.382051 1.000000
0.500000 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_01.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_02.dds"
"D:\Ymir Work\effect\pet\halloween_2022_coffin_bat_03.dds"
}
}
}
Group Particle
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
0.216000 "MOVING_TYPE_DIRECT" 0.000000 -20.000000 20.000000
}
StaticRotation 0.000000 0.000000 0.000000
Group EmitterProperty
{
MaxEmissionCount 30
CycleLength 20.000000
CycleLoopEnable 1
LoopCount 2
EmitterShape 3
EmitterAdvancedType 1
EmittingRadius 80.000000
EmitterEmitFromEdgeFlag 0
EmittingDirection 0.010000 0.010000 0.010000
List TimeEventEmittingSize
{
0.000000 0.000000
}
List TimeEventEmittingAngularVelocity
{
0.000000 0.000000
}
List TimeEventEmittingDirectionX
{
0.000000 0.000000
}
List TimeEventEmittingDirectionY
{
0.000000 0.000000
}
List TimeEventEmittingDirectionZ
{
0.000000 20.000000
}
List TimeEventEmittingVelocity
{
0.000000 1.000000
}
List TimeEventEmissionCountPerSecond
{
0.000000 1000.000000
}
List TimeEventLifeTime
{
0.000000 20.000000
}
List TimeEventSizeX
{
0.000000 128.000000
}
List TimeEventSizeY
{
0.000000 128.000000
}
}
Group ParticleProperty
{
SrcBlendType 5
DestBlendType 6
ColorOperationType 4
BillboardType 1
RotationType 4
RotationSpeed 56.000000
RotationRandomStartingBegin 360
RotationRandomStartingEnd 0
AttachEnable 0
StretchEnable 0
TexAniType 0
TexAniDelay 0.040000
TexAniRandomStartFrameEnable 0
EnableFrustum 0
List TimeEventGravity
{
}
List TimeEventAirResistance
{
0.000000 0.000000
0.034286 0.100000
}
List TimeEventScaleX
{
0.000000 0.500000
1.000000 1.000000
}
List TimeEventScaleY
{
0.000000 0.500000
1.000000 1.000000
}
List TimeEventColorRed
{
0.000000 0.819608
}
List TimeEventColorGreen
{
0.000000 0.780392
}
List TimeEventColorBlue
{
0.000000 0.682353
}
List TimeEventAlpha
{
0.000000 1.000000
0.050000 0.000000
}
List TimeEventRotation
{
0.000000 0.000000
}
List TextureFiles
{
"D:\Ymir Work\effect\monster2\smoke_dirt1.dds"
}
}
}

View File

@@ -1,35 +0,0 @@
BoundingSphereRadius 400.000000
BoundingSpherePosition -180.000000 -60.000000 650.000000
Group Mesh
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
MeshFileName "mushroomA_01.mde"
MeshAnimationLoopEnable 1
MeshAnimationLoopCount 0
MeshAnimationFrameDelay 0.020000
MeshElementCount 1
Group MeshElement00
{
BillboardType 0
BlendingEnable 1
BlendingSrcType 5
BlendingDestType 2
TextureAnimationLoopEnable 1
TextureAnimationFrameDelay 0.020000
TextureAnimationStartFrame 0
ColorOperationType 5
ColorFactor 0.168627 0.643137 0.360784 1.000000
List TimeEventAlpha
{
}
}
}

View File

@@ -1,35 +0,0 @@
BoundingSphereRadius 500.000000
BoundingSpherePosition 100.000000 0.000000 550.000000
Group Mesh
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
MeshFileName "mushroomA_03.mde"
MeshAnimationLoopEnable 1
MeshAnimationLoopCount 0
MeshAnimationFrameDelay 0.020000
MeshElementCount 1
Group MeshElement00
{
BillboardType 0
BlendingEnable 1
BlendingSrcType 5
BlendingDestType 2
TextureAnimationLoopEnable 1
TextureAnimationFrameDelay 0.020000
TextureAnimationStartFrame 0
ColorOperationType 5
ColorFactor 0.184314 0.643137 0.462745 1.000000
List TimeEventAlpha
{
}
}
}

View File

@@ -1,35 +0,0 @@
BoundingSphereRadius 330.000000
BoundingSpherePosition -160.000000 0.000000 370.000000
Group Mesh
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
MeshFileName "mushroomA_04.mde"
MeshAnimationLoopEnable 1
MeshAnimationLoopCount 0
MeshAnimationFrameDelay 0.020000
MeshElementCount 1
Group MeshElement00
{
BillboardType 0
BlendingEnable 1
BlendingSrcType 5
BlendingDestType 2
TextureAnimationLoopEnable 1
TextureAnimationFrameDelay 0.020000
TextureAnimationStartFrame 0
ColorOperationType 5
ColorFactor 0.200000 0.521569 0.227451 1.000000
List TimeEventAlpha
{
}
}
}

View File

@@ -1,36 +0,0 @@
BoundingSphereRadius 2500.000000
BoundingSpherePosition 0.000000 0.000000 1800.000000
Group Mesh
{
StartTime 0.000000
List TimeEventPosition
{
0.000000 "MOVING_TYPE_DIRECT" 0.000000 0.000000 0.000000
}
MeshFileName "turtle_statue_tree_roof_light01.mde"
MeshAnimationLoopEnable 1
MeshAnimationLoopCount 0
MeshAnimationFrameDelay 0.020000
MeshElementCount 1
Group MeshElement00
{
BillboardType 0
BlendingEnable 1
BlendingSrcType 5
BlendingDestType 2
TextureAnimationLoopEnable 1
TextureAnimationFrameDelay 0.020000
TextureAnimationStartFrame 0
ColorOperationType 5
ColorFactor 0.772549 0.733333 0.188235 1.000000
List TimeEventAlpha
{
0.046667 0.577320
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,4 @@
ScriptType MotionData
MotionFileName "D:\Ymir Work\monster\orc_lord\32_1.GR2"
MotionFileName "D:\Ymir Work\monster\orc_lord\30_1.GR2"
MotionDuration 0.833333

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -33,6 +33,9 @@ import uiAffectShower
import uiPlayerGauge
import uiCharacter
import uiTarget
import uiAutopickup
import uiStoneQueue
import uiSwitchbot
# PRIVATE_SHOP_PRICE_LIST
import uiPrivateShopBuilder
@@ -55,6 +58,157 @@ from _weakref import proxy
SCREENSHOT_CWDSAVE = False
SCREENSHOT_DIR = None
def _WriteHeadlessMapTrace(message):
if os.environ.get("M2_HEADLESS_SCENARIO", "").strip().lower() != "map_load":
return
try:
open("log/headless_map_load_trace.txt", "a").write("%s\n" % message)
except:
pass
def _GetHeadlessScenario():
return os.environ.get("M2_HEADLESS_SCENARIO", "").strip().lower()
def _WriteHeadlessTrace(message):
scenario = _GetHeadlessScenario()
if scenario == "map_load":
tracePath = "log/headless_map_load_trace.txt"
elif scenario == "gm_teleport":
tracePath = "log/headless_gm_teleport_trace.txt"
else:
return
try:
open(tracePath, "a").write("%s\n" % message)
except:
pass
def _GetHeadlessFloat(name, default):
try:
return float(os.environ.get(name, str(default)))
except (TypeError, ValueError):
return default
def _GetHeadlessWarpSteps():
rawValue = os.environ.get("M2_HEADLESS_WARP_STEPS", "").strip()
steps = []
if not rawValue:
return steps
for rawStep in rawValue.split("|"):
rawStep = rawStep.strip()
if not rawStep:
continue
parts = [part.strip() for part in rawStep.split(",")]
if len(parts) != 3:
_WriteHeadlessTrace("Invalid warp step=%s" % rawStep)
continue
mapName = parts[0]
try:
globalX = int(parts[1])
globalY = int(parts[2])
except:
_WriteHeadlessTrace("Invalid warp coords step=%s" % rawStep)
continue
steps.append({
"map_name": mapName,
"global_x": globalX,
"global_y": globalY,
})
return steps
HEADLESS_GM_STATE = {
"initialized": False,
"steps": [],
"index": 0,
"phase": "idle",
"command_at": 0.0,
"deadline": 0.0,
"quit_requested": False,
}
def _HeadlessGMEnabled():
return _GetHeadlessScenario() == "gm_teleport"
def _HeadlessGMCommandDelay():
return _GetHeadlessFloat("M2_HEADLESS_COMMAND_DELAY", 5.0)
def _HeadlessGMWarpTimeout():
return _GetHeadlessFloat("M2_HEADLESS_WARP_TIMEOUT", 20.0)
def _HeadlessGMSettleDelay():
return _GetHeadlessFloat("M2_HEADLESS_SETTLE_DELAY", 2.0)
def _HeadlessGMRequestQuit(message):
global HEADLESS_GM_STATE
if message:
_WriteHeadlessTrace(message)
if HEADLESS_GM_STATE.get("quit_requested"):
return
HEADLESS_GM_STATE["quit_requested"] = True
app.Exit()
class _HeadlessDummy(object):
def __call__(self, *args, **kwargs):
return None
def __getattr__(self, name):
return self
def __nonzero__(self):
return False
_HEADLESS_DUMMY = _HeadlessDummy()
class _HeadlessInterface(object):
def MakeInterface(self):
return None
def ShowDefaultWindows(self):
return None
def HideAllWindows(self):
return None
def Close(self):
return None
def BUILD_OnUpdate(self):
return None
def OpenWhisperDialog(self, *args):
return None
def SetMapName(self, *args):
return None
def __getattr__(self, name):
return _HEADLESS_DUMMY
cameraDistance = 1550.0
cameraPitch = 27.0
cameraRotation = 0.0
@@ -64,6 +218,7 @@ testAlignment = 0
class GameWindow(ui.ScriptWindow):
def __init__(self, stream):
_WriteHeadlessTrace("GameWindow.__init__ begin")
ui.ScriptWindow.__init__(self, "GAME")
self.SetWindowName("game")
net.SetPhaseWindow(net.PHASE_WINDOW_GAME, self)
@@ -72,6 +227,7 @@ class GameWindow(ui.ScriptWindow):
self.quickSlotPageIndex = 0
self.lastPKModeSendedTime = 0
self.pressNumber = None
self.headlessGmEnabled = _HeadlessGMEnabled()
self.guildWarQuestionDialog = None
self.interface = None
@@ -82,9 +238,16 @@ class GameWindow(ui.ScriptWindow):
self.playerGauge = None
self.stream = stream
self.interface = interfaceModule.Interface()
self.interface.MakeInterface()
self.interface.ShowDefaultWindows()
if self.headlessGmEnabled:
self.interface = _HeadlessInterface()
_WriteHeadlessTrace("GameWindow.__init__ interface_headless")
else:
self.interface = interfaceModule.Interface()
_WriteHeadlessTrace("GameWindow.__init__ interface")
self.interface.MakeInterface()
_WriteHeadlessTrace("GameWindow.__init__ interface_made")
self.interface.ShowDefaultWindows()
_WriteHeadlessTrace("GameWindow.__init__ default_windows")
self.curtain = uiPhaseCurtain.PhaseCurtain()
self.curtain.speed = 0.03
@@ -112,6 +275,7 @@ class GameWindow(ui.ScriptWindow):
self.__ServerCommand_Build()
self.__ProcessPreservedServerCommand()
_WriteHeadlessTrace("GameWindow.__init__ done")
def __del__(self):
player.SetGameWindow(0)
@@ -133,6 +297,10 @@ class GameWindow(ui.ScriptWindow):
self.enableXMasBoom = False
self.startTimeXMasBoom = 0.0
self.indexXMasBoom = 0
currentMap = background.GetCurrentMapName()
_WriteHeadlessTrace("GameWindow.Open current_map=%s" % currentMap)
if self.headlessGmEnabled:
self.__HeadlessGMOnOpen(currentMap)
global cameraDistance, cameraPitch, cameraRotation, cameraHeight
@@ -220,6 +388,7 @@ class GameWindow(ui.ScriptWindow):
self.currentCubeNPC = 0
def Close(self):
_WriteHeadlessTrace("GameWindow.Close begin current_map=%s" % background.GetCurrentMapName())
self.Hide()
global cameraDistance, cameraPitch, cameraRotation, cameraHeight
@@ -298,6 +467,7 @@ class GameWindow(ui.ScriptWindow):
app.HideCursor()
print("---------------------------------------------------------------------------- CLOSE GAME WINDOW")
_WriteHeadlessTrace("GameWindow.Close end current_map=%s" % background.GetCurrentMapName())
def __BuildKeyDict(self):
onPressKeyDict = {}
@@ -856,6 +1026,7 @@ class GameWindow(ui.ScriptWindow):
# SHOW_LOCAL_MAP_NAME
def ShowMapName(self, mapName, x, y):
_WriteHeadlessTrace("ShowMapName map=%s x=%d y=%d" % (mapName, x, y))
if self.mapNameShower:
self.mapNameShower.ShowMapName(mapName, x, y)
@@ -1465,8 +1636,104 @@ class GameWindow(ui.ScriptWindow):
def OnMouseMiddleButtonUp(self):
player.SetMouseMiddleButtonState(player.MBS_CLICK)
def __HeadlessGMOnOpen(self, currentMap):
global HEADLESS_GM_STATE
state = HEADLESS_GM_STATE
now = app.GetTime()
if not state["initialized"]:
state["steps"] = _GetHeadlessWarpSteps()
state["index"] = 0
state["phase"] = "waiting_command"
state["command_at"] = now + _HeadlessGMCommandDelay()
state["deadline"] = 0.0
state["quit_requested"] = False
state["initialized"] = True
_WriteHeadlessTrace("GM ready current_map=%s steps=%d" % (currentMap, len(state["steps"])))
if not state["steps"]:
state["phase"] = "failed"
_HeadlessGMRequestQuit("No warp steps configured")
return
if state["phase"] != "waiting_open":
return
if state["index"] >= len(state["steps"]):
state["phase"] = "success"
_HeadlessGMRequestQuit("Scenario success current_map=%s" % currentMap)
return
expectedMap = state["steps"][state["index"]]["map_name"]
if currentMap != expectedMap:
state["phase"] = "failed"
_HeadlessGMRequestQuit("Warp open mismatch index=%d expected=%s current_map=%s" % (state["index"], expectedMap, currentMap))
return
_WriteHeadlessTrace("Warp arrived index=%d map=%s" % (state["index"], currentMap))
state["index"] += 1
if state["index"] >= len(state["steps"]):
state["phase"] = "success"
_HeadlessGMRequestQuit("Scenario success current_map=%s" % currentMap)
return
state["phase"] = "settling"
state["command_at"] = now + _HeadlessGMSettleDelay()
state["deadline"] = 0.0
def __HeadlessGMOnUpdate(self):
global HEADLESS_GM_STATE
if not self.headlessGmEnabled:
return
state = HEADLESS_GM_STATE
if not state["initialized"] or state["quit_requested"]:
return
now = app.GetTime()
if state["phase"] == "waiting_command":
if state["index"] >= len(state["steps"]):
state["phase"] = "success"
_HeadlessGMRequestQuit("Scenario success current_map=%s" % background.GetCurrentMapName())
return
if now < state["command_at"]:
return
if 0 == player.GetMainCharacterIndex():
return
step = state["steps"][state["index"]]
meterX = int(step["global_x"] / 100)
meterY = int(step["global_y"] / 100)
command = "/warp %d %d" % (meterX, meterY)
_WriteHeadlessTrace("Warp send index=%d map=%s meter_x=%d meter_y=%d current_map=%s" % (state["index"], step["map_name"], meterX, meterY, background.GetCurrentMapName()))
net.SendChatPacket(command)
state["phase"] = "waiting_open"
state["deadline"] = now + _HeadlessGMWarpTimeout()
return
if state["phase"] == "settling":
if now >= state["command_at"]:
state["phase"] = "waiting_command"
return
if state["phase"] == "waiting_open" and now > state["deadline"]:
if state["index"] < len(state["steps"]):
expectedMap = state["steps"][state["index"]]["map_name"]
else:
expectedMap = ""
state["phase"] = "failed"
_HeadlessGMRequestQuit("Warp timeout index=%d expected=%s current_map=%s" % (state["index"], expectedMap, background.GetCurrentMapName()))
return
def OnUpdate(self):
app.UpdateGame()
self.__HeadlessGMOnUpdate()
if self.mapNameShower.IsShow():
self.mapNameShower.Update()
@@ -1913,6 +2180,10 @@ class GameWindow(ui.ScriptWindow):
"PartyRequestDenied" : self.__PartyRequestDenied,
"horse_state" : self.__Horse_UpdateState,
"hide_horse_state" : self.__Horse_HideState,
"AutoPickupState" : self.__AutoPickupState,
"StoneQueueState" : self.__StoneQueueState,
"SwitchbotState" : self.__SwitchbotState,
"SwitchbotSlot" : self.__SwitchbotSlot,
"WarUC" : self.__GuildWar_UpdateMemberCount,
"test_server" : self.__EnableTestServerFlag,
"mall" : self.__InGameShop_Show,
@@ -1960,6 +2231,18 @@ class GameWindow(ui.ScriptWindow):
def PartyHealReady(self):
self.interface.PartyHealReady()
def __AutoPickupState(self, enabled, mode, mask, vip):
uiAutopickup.SetAutoPickupState(int(enabled), int(mode), int(mask), int(vip))
def __StoneQueueState(self, maxCount, active, current, success, fail):
uiStoneQueue.SetStoneQueueState(int(maxCount), int(active), int(current), int(success), int(fail))
def __SwitchbotState(self, speedIndex, activeCount, scrollCount, etaSeconds):
uiSwitchbot.SetSwitchbotState(int(speedIndex), int(activeCount), int(scrollCount), int(etaSeconds))
def __SwitchbotSlot(self, slotIndex, active, itemCell, attrType, minValue, attempts):
uiSwitchbot.SetSwitchbotSlotState(int(slotIndex), int(active), int(itemCell), int(attrType), int(minValue), int(attempts))
def AskSafeboxPassword(self):
self.interface.AskSafeboxPassword()
@@ -2229,4 +2512,3 @@ class GameWindow(ui.ScriptWindow):
def SkillClearCoolTime(self, slotIndex):
self.interface.SkillClearCoolTime(slotIndex)

View File

@@ -25,6 +25,9 @@ import uiSystem
import uiRestart
import uiToolTip
import uiMiniMap
import uiBiolog
import uiTeleport
import uiSwitchbot
import uiParty
import uiSafebox
import uiGuild
@@ -75,6 +78,9 @@ class Interface(object):
self.wndChat = None
self.wndMessenger = None
self.wndMiniMap = None
self.wndBiolog = None
self.wndTeleport = None
self.wndSwitchbot = None
self.wndGuild = None
self.wndGuildBuilding = None
@@ -180,6 +186,9 @@ class Interface(object):
wndDragonSoulRefine = None
wndMiniMap = uiMiniMap.MiniMap()
wndBiolog = uiBiolog.BiologWindow()
wndTeleport = uiTeleport.TeleportWindow()
wndSwitchbot = uiSwitchbot.SwitchbotWindow()
wndSafebox = uiSafebox.SafeboxWindow()
# ITEM_MALL
@@ -195,8 +204,14 @@ class Interface(object):
self.wndDragonSoul = wndDragonSoul
self.wndDragonSoulRefine = wndDragonSoulRefine
self.wndMiniMap = wndMiniMap
self.wndBiolog = wndBiolog
self.wndTeleport = wndTeleport
self.wndSwitchbot = wndSwitchbot
self.wndSafebox = wndSafebox
self.wndChatLog = wndChatLog
self.wndMiniMap.SetBiologButtonEvent(ui.__mem_func__(self.ToggleBiologWindow))
self.wndMiniMap.SetTeleportButtonEvent(ui.__mem_func__(self.ToggleTeleportWindow))
self.wndMiniMap.SetSwitchbotButtonEvent(ui.__mem_func__(self.ToggleSwitchbotWindow))
if app.ENABLE_DRAGON_SOUL_SYSTEM:
self.wndDragonSoul.SetDragonSoulRefineWindow(self.wndDragonSoulRefine)
@@ -409,6 +424,11 @@ class Interface(object):
if self.wndMiniMap:
self.wndMiniMap.Destroy()
if self.wndBiolog:
self.wndBiolog.Hide()
if self.wndSwitchbot:
self.wndSwitchbot.Hide()
if self.wndSafebox:
self.wndSafebox.Destroy()
@@ -499,6 +519,8 @@ class Interface(object):
del self.tooltipItem
del self.tooltipSkill
del self.wndMiniMap
del self.wndBiolog
del self.wndSwitchbot
del self.wndSafebox
del self.wndMall
del self.wndParty
@@ -858,6 +880,9 @@ class Interface(object):
if self.wndMiniMap:
self.wndMiniMap.Hide()
if self.wndBiolog:
self.wndBiolog.Hide()
if self.wndMessenger:
self.wndMessenger.Hide()
@@ -942,6 +967,33 @@ class Interface(object):
def MiniMapScaleDown(self):
self.wndMiniMap.ScaleDown()
def ToggleBiologWindow(self):
if False == self.wndBiolog.IsShow():
(miniMapX, miniMapY) = self.wndMiniMap.GetGlobalPosition()
self.wndBiolog.SetPosition(max(10, miniMapX - self.wndBiolog.GetWidth() - 10), miniMapY + 8)
self.wndBiolog.Show()
self.wndBiolog.SetTop()
else:
self.wndBiolog.Hide()
def ToggleTeleportWindow(self):
if False == self.wndTeleport.IsShow():
(miniMapX, miniMapY) = self.wndMiniMap.GetGlobalPosition()
self.wndTeleport.SetPosition(max(10, miniMapX - self.wndTeleport.GetWidth() - 10), miniMapY + 24)
self.wndTeleport.Show()
self.wndTeleport.SetTop()
else:
self.wndTeleport.Hide()
def ToggleSwitchbotWindow(self):
if False == self.wndSwitchbot.IsShow():
(miniMapX, miniMapY) = self.wndMiniMap.GetGlobalPosition()
self.wndSwitchbot.SetPosition(max(10, miniMapX - self.wndSwitchbot.GetWidth() - 10), miniMapY + 40)
self.wndSwitchbot.Show()
self.wndSwitchbot.SetTop()
else:
self.wndSwitchbot.Hide()
def ToggleCharacterWindow(self, state):
if False == player.IsObserverMode():
if False == self.wndCharacter.IsShow():

View File

@@ -1,3 +1,5 @@
import os
import ui
import uiScriptLocale
import net
@@ -35,6 +37,25 @@ import uiOption
import uiRestart
####################################
def _GetHeadlessScenario():
return os.environ.get("M2_HEADLESS_SCENARIO", "").strip().lower()
def _WriteHeadlessTrace(message):
scenario = _GetHeadlessScenario()
if scenario == "map_load":
tracePath = "log/headless_map_load_trace.txt"
elif scenario == "gm_teleport":
tracePath = "log/headless_gm_teleport_trace.txt"
else:
return
try:
open(tracePath, "a").write("%s\n" % message)
except:
pass
class LoadingWindow(ui.ScriptWindow):
def __init__(self, stream):
print("NEW LOADING WINDOW -------------------------------------------------------------------------------")
@@ -218,6 +239,7 @@ class LoadingWindow(ui.ScriptWindow):
try:
runFunc()
except:
_WriteHeadlessTrace("LoadData failure step=%d" % progress)
self.errMsg.Show()
self.loadStepList=[]
@@ -302,7 +324,9 @@ class LoadingWindow(ui.ScriptWindow):
emotion.RegisterEmotionIcons()
def __LoadMap(self):
_WriteHeadlessTrace("LoadMap begin global_x=%d global_y=%d" % (self.playerX, self.playerY))
net.Warp(self.playerX, self.playerY)
_WriteHeadlessTrace("LoadMap current_map=%s" % background.GetCurrentMapName())
def __LoadSound(self):
playerSettingModule.LoadGameData("SOUND")
@@ -337,6 +361,7 @@ class LoadingWindow(ui.ScriptWindow):
# END_OF_GUILD_BUILDING
def __StartGame(self):
_WriteHeadlessTrace("StartGame begin current_map=%s" % background.GetCurrentMapName())
background.SetViewDistanceSet(background.DISTANCE0, 25600)
"""
background.SetViewDistanceSet(background.DISTANCE1, 19200)
@@ -349,6 +374,7 @@ class LoadingWindow(ui.ScriptWindow):
app.SetGlobalCenterPosition(self.playerX, self.playerY)
net.StartGame()
_WriteHeadlessTrace("StartGame queued current_map=%s" % background.GetCurrentMapName())
def _ReloadTitleNames():
for i in range(len(localeInfo.TITLE_NAME_LIST)):

View File

@@ -1,3 +1,4 @@
import os
import dbg
import app
import net
@@ -18,6 +19,10 @@ import ime
import uiScriptLocale
import debugInfo
def _AllowHeadlessLoginInfo():
return os.environ.get("M2_HEADLESS_SCENARIO", "").strip().lower() == "gm_teleport"
# Multi-language hot-reload system
from uilocaleselector import LocaleSelector
@@ -715,7 +720,7 @@ class LoginWindow(ui.ScriptWindow):
def __LoadLoginInfo(self, loginInfoFileName):
# This should not work in production
if not debugInfo.IsDebugMode():
if not debugInfo.IsDebugMode() and not _AllowHeadlessLoginInfo():
app.loggined = FALSE
else:
try:

View File

@@ -1,6 +1,8 @@
###################################################################################################
# Network
import os
import app
import chr
import dbg
@@ -18,6 +20,25 @@ import uiPhaseCurtain
import localeInfo
def _GetHeadlessScenario():
return os.environ.get("M2_HEADLESS_SCENARIO", "").strip().lower()
def _WriteHeadlessTrace(message):
scenario = _GetHeadlessScenario()
if scenario == "map_load":
tracePath = "log/headless_map_load_trace.txt"
elif scenario == "gm_teleport":
tracePath = "log/headless_gm_teleport_trace.txt"
else:
return
try:
open(tracePath, "a").write("%s\n" % message)
except:
pass
class PopupDialog(ui.ScriptWindow):
# MR-15: Multiline dialog messages
BASE_HEIGHT = 105
@@ -167,6 +188,7 @@ class MainStream(object):
if newPhaseWindow:
newPhaseWindow.Open()
_WriteHeadlessTrace("MainStream.ChangePhase opened=%s" % newPhaseWindow.__class__.__name__)
self.curPhaseWindow=newPhaseWindow
@@ -237,7 +259,7 @@ class MainStream(object):
try:
import introLoading
loadingPhaseWindow=introLoading.LoadingWindow(self)
loadingPhaseWindow.LoadData(x, y)
loadingPhaseWindow.DEBUG_LoadData(x, y)
self.SetPhaseWindow(loadingPhaseWindow)
except:
import exception
@@ -256,8 +278,10 @@ class MainStream(object):
def SetGamePhase(self):
try:
import game
_WriteHeadlessTrace("MainStream.SetGamePhase begin current_map=%s" % background.GetCurrentMapName())
self.popupWindow.Close()
self.SetPhaseWindow(game.GameWindow(self))
_WriteHeadlessTrace("MainStream.SetGamePhase queued current_map=%s" % background.GetCurrentMapName())
except:
raise
import exception

View File

@@ -659,6 +659,7 @@ def __LoadGameWarriorEx(race, path):
## Bone
chrmgr.RegisterAttachingBoneName(chr.PART_WEAPON, "equip_right_hand")
chrmgr.RegisterAttachingBoneName(chr.PART_ACCE, "Bip01 Spine2")
def __LoadGameAssassinEx(race, path):
## Assassin
@@ -872,6 +873,7 @@ def __LoadGameAssassinEx(race, path):
chrmgr.RegisterAttachingBoneName(chr.PART_WEAPON, "equip_right")
chrmgr.RegisterAttachingBoneName(chr.PART_WEAPON_LEFT, "equip_left")
chrmgr.RegisterAttachingBoneName(chr.PART_ACCE, "Bip01 Spine2")
def __LoadGameSuraEx(race, path):
## Sura
@@ -1189,6 +1191,7 @@ def __LoadGameShamanEx(race, path):
chrmgr.RegisterAttachingBoneName(chr.PART_WEAPON, "equip_right")
chrmgr.RegisterAttachingBoneName(chr.PART_WEAPON_LEFT, "equip_left")
chrmgr.RegisterAttachingBoneName(chr.PART_ACCE, "Bip01 Spine2")
def __LoadGameSkill():
@@ -1456,4 +1459,4 @@ def SetGuildBuilding(race, name, grade):
chrmgr.SetPathName("d:/ymir work/guild/building/%s/" % name)
chrmgr.LoadRaceData("%s%02d.msm" % (name, grade))
chrmgr.RegisterMotionMode(chr.MOTION_MODE_GENERAL)
#chrmgr.RegisterMotionData(chr.MOTION_MODE_GENERAL, chr.MOTION_DEAD, name + "_destruction.msa")
#chrmgr.RegisterMotionData(chr.MOTION_MODE_GENERAL, chr.MOTION_DEAD, name + "_destruction.msa")

View File

@@ -1,3 +1,5 @@
import os
import dbg
import app
import localeInfo
@@ -14,6 +16,66 @@ import stringCommander
#bind_me(locals().values())
def _GetHeadlessScenario():
return os.environ.get("M2_HEADLESS_SCENARIO", "").strip().lower()
def _WriteHeadlessTrace(message):
scenario = _GetHeadlessScenario()
if scenario == "map_load":
tracePath = "log/headless_map_load_trace.txt"
elif scenario == "gm_teleport":
tracePath = "log/headless_gm_teleport_trace.txt"
else:
return
try:
open(tracePath, "a").write("%s\n" % message)
except:
pass
def _GetHeadlessInt(name, default):
try:
return int(os.environ.get(name, str(default)))
except (TypeError, ValueError):
return default
def _ApplyRenderFPSOverride():
rawValue = os.environ.get("M2_RENDER_FPS", "").strip()
if not rawValue:
return
try:
fps = int(rawValue)
except (TypeError, ValueError):
_WriteHeadlessTrace("Invalid M2_RENDER_FPS=%s" % rawValue)
return
app.SetFPS(fps)
_WriteHeadlessTrace("Render FPS override=%d" % fps)
def _SetInitialPhase(mainStream):
scenario = _GetHeadlessScenario()
if scenario == "map_load":
mapName = os.environ.get("M2_HEADLESS_MAP_NAME", "").strip()
globalX = _GetHeadlessInt("M2_HEADLESS_GLOBAL_X", 460800)
globalY = _GetHeadlessInt("M2_HEADLESS_GLOBAL_Y", 960000)
_WriteHeadlessTrace("Scenario begin map=%s global_x=%d global_y=%d" % (mapName, globalX, globalY))
mainStream.SetTestGamePhase(globalX, globalY)
return
if scenario == "gm_teleport":
_WriteHeadlessTrace("Scenario begin gm_teleport")
mainStream.SetLoginPhase()
return
mainStream.SetLoginPhase()
def RunApp():
musicInfo.LoadLastPlayFieldMusic()
@@ -36,6 +98,7 @@ def RunApp():
return
app.SetCamera(1500.0, 30.0, 0.0, 180.0)
_ApplyRenderFPSOverride()
#Gets and sets the floating-point control word
#app.SetControlFP()
@@ -47,8 +110,7 @@ def RunApp():
mainStream.Create()
#mainStream.SetLoadingPhase()
mainStream.SetLoginPhase()
_SetInitialPhase(mainStream)
#mainStream.SetSelectCharacterPhase()
#mainStream.SetCreateCharacterPhase()
#mainStream.SetSelectEmpirePhase()
@@ -58,4 +120,3 @@ def RunApp():
mainStream.Destroy()
RunApp()

145
assets/root/uiAutopickup.py Normal file
View File

@@ -0,0 +1,145 @@
import net
import ui
STATE_ENABLED = 0
STATE_MODE = 0
STATE_MASK = 31
STATE_VIP = 0
OPEN_WINDOWS = []
FILTERS = (
(1, "Weapons"),
(2, "Armor"),
(4, "Yang"),
(8, "Stones"),
(16, "Materials"),
)
def SetAutoPickupState(enabled, mode, mask, vip):
global STATE_ENABLED
global STATE_MODE
global STATE_MASK
global STATE_VIP
STATE_ENABLED = 1 if int(enabled) else 0
STATE_MODE = 1 if int(mode) else 0
STATE_MASK = int(mask) & 31
STATE_VIP = 1 if int(vip) else 0
for window in OPEN_WINDOWS:
try:
window.ApplyState()
except:
pass
class AutoPickupWindow(ui.BoardWithTitleBar):
def __init__(self):
ui.BoardWithTitleBar.__init__(self)
OPEN_WINDOWS.append(self)
self.filterButtons = {}
self.AddFlag("float")
self.AddFlag("movable")
self.SetSize(280, 248)
self.SetTitleName("Auto Pickup")
self.SetCloseEvent(self.Hide)
self.__CreateChildren()
self.Hide()
def __del__(self):
if self in OPEN_WINDOWS:
OPEN_WINDOWS.remove(self)
ui.BoardWithTitleBar.__del__(self)
def Destroy(self):
if self in OPEN_WINDOWS:
OPEN_WINDOWS.remove(self)
self.ClearDictionary()
self.filterButtons = {}
def __CreateChildren(self):
self.statusLine = self.__CreateLine(15, 36)
self.rangeLine = self.__CreateLine(15, 56)
self.modeLine = self.__CreateLine(15, 76)
self.noteLine = self.__CreateLine(15, 96)
self.enableButton = self.__CreateButton(170, 34, 90, "Enable")
self.enableButton.SetEvent(self.__ToggleEnabled)
self.whitelistButton = self.__CreateButton(15, 118, 118, "Whitelist")
self.whitelistButton.SetEvent(self.__SetMode, 0)
self.blacklistButton = self.__CreateButton(142, 118, 118, "Blacklist")
self.blacklistButton.SetEvent(self.__SetMode, 1)
for index, filterData in enumerate(FILTERS):
(bit, label) = filterData
button = self.__CreateButton(15, 150 + index * 18, 245, label)
button.SetEvent(self.__ToggleFilter, bit)
self.filterButtons[bit] = button
def __CreateLine(self, x, y):
textLine = ui.TextLine()
textLine.SetParent(self)
textLine.SetPosition(x, y)
textLine.SetOutline()
textLine.Show()
return textLine
def __CreateButton(self, x, y, width, text):
button = ui.Button()
button.SetParent(self)
button.SetPosition(x, y)
button.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
button.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
button.SetDisableVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetSize(width, 17)
button.SetText(text)
button.Show()
return button
def __Send(self, command):
net.SendChatPacket(command, 0)
def __ToggleEnabled(self):
SetAutoPickupState(0 if STATE_ENABLED else 1, STATE_MODE, STATE_MASK, STATE_VIP)
self.__Send("/autopickup enable %d" % STATE_ENABLED)
def __SetMode(self, mode):
SetAutoPickupState(STATE_ENABLED, mode, STATE_MASK, STATE_VIP)
self.__Send("/autopickup mode %s" % ("blacklist" if mode else "whitelist"))
def __ToggleFilter(self, bit):
mask = STATE_MASK ^ bit
SetAutoPickupState(STATE_ENABLED, STATE_MODE, mask, STATE_VIP)
self.__Send("/autopickup mask %d" % STATE_MASK)
def ApplyState(self):
self.statusLine.SetText("Status: %s" % ("Enabled" if STATE_ENABLED else "Disabled"))
self.rangeLine.SetText("Range: %s" % ("VIP 300 / Free 220" if STATE_VIP else "Free 220"))
self.modeLine.SetText("Mode: %s" % ("Blacklist" if STATE_MODE else "Whitelist"))
self.noteLine.SetText("Only nearby owned drops are valid")
self.enableButton.SetText("Disable" if STATE_ENABLED else "Enable")
if STATE_MODE == 0:
self.whitelistButton.Down()
self.blacklistButton.SetUp()
else:
self.whitelistButton.SetUp()
self.blacklistButton.Down()
for (bit, label) in FILTERS:
prefix = "[x]" if (STATE_MASK & bit) else "[ ]"
self.filterButtons[bit].SetText("%s %s" % (prefix, label))
def Show(self):
self.__Send("/autopickup sync")
self.ApplyState()
ui.BoardWithTitleBar.Show(self)

145
assets/root/uiBiolog.py Normal file
View File

@@ -0,0 +1,145 @@
import time
import net
import player
import quest
import ui
class BiologWindow(ui.BoardWithTitleBar):
TITLE_PREFIX = "Biolog Stage "
STAGE_TARGETS = {
1 : 10,
2 : 15,
3 : 20,
4 : 20,
5 : 25,
6 : 30,
7 : 30,
8 : 40,
9 : 40,
}
def __init__(self):
ui.BoardWithTitleBar.__init__(self)
self.lastRefreshTime = 0.0
self.AddFlag("float")
self.AddFlag("movable")
self.SetSize(230, 165)
self.SetTitleName("Biolog")
self.SetCloseEvent(self.Hide)
self.__CreateChildren()
self.Hide()
def __del__(self):
ui.BoardWithTitleBar.__del__(self)
def __CreateChildren(self):
self.statusLine = self.__CreateValueLine(15, 36)
self.stageLine = self.__CreateValueLine(15, 58)
self.itemLine = self.__CreateValueLine(15, 80)
self.progressLine = self.__CreateValueLine(15, 102)
self.cooldownLine = self.__CreateValueLine(15, 124)
submitButton = ui.Button()
submitButton.SetParent(self)
submitButton.SetPosition(134, 132)
submitButton.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
submitButton.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
submitButton.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
submitButton.SetText("Submit")
submitButton.SetEvent(self.__OnSubmit)
submitButton.Show()
self.submitButton = submitButton
def __CreateValueLine(self, x, y):
textLine = ui.TextLine()
textLine.SetParent(self)
textLine.SetPosition(x, y)
textLine.SetOutline()
textLine.Show()
return textLine
def __GetBiologData(self):
questCount = min(quest.GetQuestCount(), quest.QUEST_MAX_NUM)
for questIndex in range(questCount):
(questName, questIcon, questCounterName, questCounterValue) = quest.GetQuestData(questIndex)
if not questName.startswith(self.TITLE_PREFIX):
continue
try:
stageIndex = int(questName[len(self.TITLE_PREFIX):])
except:
continue
totalRequired = self.STAGE_TARGETS.get(stageIndex, questCounterValue)
(clockName, clockValue) = quest.GetQuestLastTime(questIndex)
return {
"stage" : stageIndex,
"itemName" : questCounterName,
"remaining" : max(questCounterValue, 0),
"required" : max(totalRequired, 0),
"clockName" : clockName,
"clockValue" : max(clockValue, 0),
}
return None
def __FormatCooldown(self, seconds):
hours = seconds // 3600
minutes = (seconds // 60) % 60
return "%02d:%02d:%02d" % (hours, minutes, seconds % 60)
def __RefreshLockedState(self):
if player.GetStatus(player.LEVEL) < 30:
self.statusLine.SetText("Status: Unlocks at level 30")
else:
self.statusLine.SetText("Status: No active biolog stage")
self.stageLine.SetText("Stage: -")
self.itemLine.SetText("Item: -")
self.progressLine.SetText("Progress: -")
self.cooldownLine.SetText("Cooldown: -")
self.submitButton.Hide()
def Refresh(self):
biologData = self.__GetBiologData()
if not biologData:
self.__RefreshLockedState()
return
submitted = max(biologData["required"] - biologData["remaining"], 0)
self.statusLine.SetText("Status: %s" % ("Ready" if biologData["clockValue"] <= 0 else "Cooldown"))
self.stageLine.SetText("Stage: %d / 9" % biologData["stage"])
self.itemLine.SetText("Item: %s" % biologData["itemName"])
self.progressLine.SetText("Progress: %d / %d" % (submitted, biologData["required"]))
if biologData["clockValue"] > 0 and len(biologData["clockName"]) > 0:
self.cooldownLine.SetText("Cooldown: %s" % self.__FormatCooldown(biologData["clockValue"]))
else:
self.cooldownLine.SetText("Cooldown: Ready")
self.submitButton.Show()
def Show(self):
self.Refresh()
ui.BoardWithTitleBar.Show(self)
def OnUpdate(self):
currentTime = time.time()
if currentTime - self.lastRefreshTime < 0.2:
return
self.lastRefreshTime = currentTime
self.Refresh()
def __OnSubmit(self):
try:
net.SendBiologSubmit()
except AttributeError:
net.SendChatPacket("/biolog_submit", 0)
self.lastRefreshTime = 0.0
self.Refresh()

233
assets/root/uiStoneQueue.py Normal file
View File

@@ -0,0 +1,233 @@
import item
import net
import player
import ui
STATE_MAX = 3
STATE_ACTIVE = 0
STATE_CURRENT = 0
STATE_SUCCESS = 0
STATE_FAIL = 0
OPEN_WINDOWS = []
def SetStoneQueueState(maxCount, active, current, success, fail):
global STATE_MAX
global STATE_ACTIVE
global STATE_CURRENT
global STATE_SUCCESS
global STATE_FAIL
STATE_MAX = max(1, int(maxCount))
STATE_ACTIVE = 1 if int(active) else 0
STATE_CURRENT = max(0, int(current))
STATE_SUCCESS = max(0, int(success))
STATE_FAIL = max(0, int(fail))
for window in OPEN_WINDOWS:
try:
window.Refresh()
except:
pass
class StoneQueueWindow(ui.BoardWithTitleBar):
PAGE_SIZE = 20
def __init__(self):
ui.BoardWithTitleBar.__init__(self)
OPEN_WINDOWS.append(self)
self.scrollSlotPos = -1
self.selectedSlots = []
self.eligibleSlots = []
self.pageIndex = 0
self.candidateButtons = []
self.queueLines = []
self.AddFlag("float")
self.AddFlag("movable")
self.SetSize(340, 355)
self.SetTitleName("Stone Queue")
self.SetCloseEvent(self.Hide)
self.__CreateChildren()
self.Hide()
def __del__(self):
if self in OPEN_WINDOWS:
OPEN_WINDOWS.remove(self)
ui.BoardWithTitleBar.__del__(self)
def Destroy(self):
if self in OPEN_WINDOWS:
OPEN_WINDOWS.remove(self)
self.ClearDictionary()
self.selectedSlots = []
self.eligibleSlots = []
self.candidateButtons = []
self.queueLines = []
def __CreateChildren(self):
self.scrollLine = self.__CreateLine(15, 36)
self.statusLine = self.__CreateLine(15, 56)
self.progressLine = self.__CreateLine(15, 76)
self.resultLine = self.__CreateLine(15, 96)
self.prevButton = self.__CreateButton(15, 118, 45, "Prev")
self.prevButton.SetEvent(self.__ChangePage, -1)
self.pageLine = self.__CreateLine(127, 122)
self.nextButton = self.__CreateButton(280, 118, 45, "Next")
self.nextButton.SetEvent(self.__ChangePage, 1)
for row in range(5):
for column in range(4):
button = self.__CreateButton(15 + column * 78, 145 + row * 24, 72, "-")
self.candidateButtons.append(button)
for index in range(8):
line = self.__CreateLine(15, 272 + index * 10)
self.queueLines.append(line)
self.startButton = self.__CreateButton(185, 328, 65, "Start")
self.startButton.SetEvent(self.__StartQueue)
self.cancelButton = self.__CreateButton(260, 328, 65, "Cancel")
self.cancelButton.SetEvent(self.__CancelQueue)
def __CreateLine(self, x, y):
textLine = ui.TextLine()
textLine.SetParent(self)
textLine.SetPosition(x, y)
textLine.SetOutline()
textLine.Show()
return textLine
def __CreateButton(self, x, y, width, text):
button = ui.Button()
button.SetParent(self)
button.SetPosition(x, y)
button.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
button.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
button.SetDisableVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetSize(width, 17)
button.SetText(text)
button.Show()
return button
def __BuildEligibleSlots(self):
self.eligibleSlots = []
if self.scrollSlotPos < 0:
return
scrollIndex = player.GetItemIndex(self.scrollSlotPos)
for slot in range(player.INVENTORY_PAGE_SIZE * player.INVENTORY_PAGE_COUNT):
if player.REFINE_OK != player.CanRefine(scrollIndex, slot):
continue
targetIndex = player.GetItemIndex(slot)
if targetIndex == 0:
continue
item.SelectItem(targetIndex)
if item.GetItemType() != item.ITEM_TYPE_METIN:
continue
self.eligibleSlots.append(slot)
def __GetSlotLabel(self, slot):
itemVnum = player.GetItemIndex(slot)
if itemVnum == 0:
return "Slot %d" % slot
item.SelectItem(itemVnum)
return "%d:+%d" % (slot, player.GetItemGrade(slot))
def __RefreshCandidates(self):
start = self.pageIndex * self.PAGE_SIZE
end = start + self.PAGE_SIZE
pageSlots = self.eligibleSlots[start:end]
totalPages = max(1, (len(self.eligibleSlots) + self.PAGE_SIZE - 1) // self.PAGE_SIZE)
self.pageLine.SetText("Page %d / %d" % (self.pageIndex + 1, totalPages))
for index, button in enumerate(self.candidateButtons):
if index < len(pageSlots):
slot = pageSlots[index]
prefix = "[x]" if slot in self.selectedSlots else "[ ]"
button.SetText("%s %s" % (prefix, self.__GetSlotLabel(slot)))
button.Enable()
button.SetEvent(self.__ToggleSlot, slot)
else:
button.SetText("-")
button.Disable()
self.prevButton.Enable() if self.pageIndex > 0 else self.prevButton.Disable()
self.nextButton.Enable() if end < len(self.eligibleSlots) else self.nextButton.Disable()
def __RefreshQueueLines(self):
for index, line in enumerate(self.queueLines):
if index < len(self.selectedSlots):
line.SetText("Queue %d: %s" % (index + 1, self.__GetSlotLabel(self.selectedSlots[index])))
else:
line.SetText("Queue %d: -" % (index + 1))
def __ToggleSlot(self, slot):
if STATE_ACTIVE:
return
if slot in self.selectedSlots:
self.selectedSlots.remove(slot)
elif len(self.selectedSlots) < STATE_MAX:
self.selectedSlots.append(slot)
self.Refresh()
def __ChangePage(self, delta):
totalPages = max(1, (len(self.eligibleSlots) + self.PAGE_SIZE - 1) // self.PAGE_SIZE)
self.pageIndex = max(0, min(totalPages - 1, self.pageIndex + delta))
self.__RefreshCandidates()
def __StartQueue(self):
if STATE_ACTIVE or self.scrollSlotPos < 0 or not self.selectedSlots:
return
command = "/stone_queue start %d %s" % (
self.scrollSlotPos,
" ".join([str(slot) for slot in self.selectedSlots[:STATE_MAX]]),
)
net.SendChatPacket(command, 0)
def __CancelQueue(self):
net.SendChatPacket("/stone_queue cancel", 0)
def Open(self, scrollSlotPos, targetSlotPos):
self.scrollSlotPos = scrollSlotPos
self.pageIndex = 0
self.selectedSlots = []
self.__BuildEligibleSlots()
if targetSlotPos in self.eligibleSlots:
self.selectedSlots.append(targetSlotPos)
net.SendChatPacket("/stone_queue sync", 0)
self.Refresh()
self.SetTop()
self.Show()
def Refresh(self):
self.scrollLine.SetText("Scroll slot: %d" % self.scrollSlotPos)
self.statusLine.SetText("Status: %s" % ("Running" if STATE_ACTIVE else "Ready"))
self.progressLine.SetText("Progress: %d / %d" % (STATE_CURRENT, len(self.selectedSlots)))
self.resultLine.SetText("Results: %d success / %d fail / max %d" % (STATE_SUCCESS, STATE_FAIL, STATE_MAX))
self.__RefreshCandidates()
self.__RefreshQueueLines()
if STATE_ACTIVE:
self.startButton.Disable()
self.cancelButton.Enable()
else:
self.startButton.Enable()
self.cancelButton.Disable()

455
assets/root/uiSwitchbot.py Normal file
View File

@@ -0,0 +1,455 @@
import item
import net
import player
import ui
import uitooltip
STATE_SPEED = 1
STATE_ACTIVE_COUNT = 0
STATE_SCROLL_COUNT = 0
STATE_ETA_SECONDS = 0
STATE_SLOTS = []
OPEN_WINDOWS = []
_AFFECT_HELPER = None
for _slotIndex in range(5):
STATE_SLOTS.append({
"active": 0,
"itemCell": 0,
"attrType": 0,
"minValue": 0,
"attempts": 0,
})
def _GetAffectHelper():
global _AFFECT_HELPER
if _AFFECT_HELPER is None:
_AFFECT_HELPER = uitooltip.ItemToolTip()
return _AFFECT_HELPER
def _GetAffectLabel(attrType):
if attrType <= 0:
return "-"
try:
label = _GetAffectHelper()._ItemToolTip__GetAffectString(attrType, 1)
except:
label = None
if not label:
return "Attr %d" % attrType
return label
ATTR_OPTIONS = (
(item.APPLY_MAX_HP, _GetAffectLabel(item.APPLY_MAX_HP)),
(item.APPLY_MAX_SP, _GetAffectLabel(item.APPLY_MAX_SP)),
(item.APPLY_STR, _GetAffectLabel(item.APPLY_STR)),
(item.APPLY_DEX, _GetAffectLabel(item.APPLY_DEX)),
(item.APPLY_CON, _GetAffectLabel(item.APPLY_CON)),
(item.APPLY_INT, _GetAffectLabel(item.APPLY_INT)),
(item.APPLY_ATT_SPEED, _GetAffectLabel(item.APPLY_ATT_SPEED)),
(item.APPLY_MOV_SPEED, _GetAffectLabel(item.APPLY_MOV_SPEED)),
(item.APPLY_CAST_SPEED, _GetAffectLabel(item.APPLY_CAST_SPEED)),
(item.APPLY_CRITICAL_PCT, _GetAffectLabel(item.APPLY_CRITICAL_PCT)),
(item.APPLY_PENETRATE_PCT, _GetAffectLabel(item.APPLY_PENETRATE_PCT)),
(item.APPLY_ATTBONUS_MONSTER, _GetAffectLabel(item.APPLY_ATTBONUS_MONSTER)),
(item.APPLY_ATTBONUS_DEVIL, _GetAffectLabel(item.APPLY_ATTBONUS_DEVIL)),
(item.APPLY_ATTBONUS_HUMAN, _GetAffectLabel(item.APPLY_ATTBONUS_HUMAN)),
(item.APPLY_STEAL_HP, _GetAffectLabel(item.APPLY_STEAL_HP)),
(item.APPLY_STEAL_SP, _GetAffectLabel(item.APPLY_STEAL_SP)),
(item.APPLY_BLOCK, _GetAffectLabel(item.APPLY_BLOCK)),
(item.APPLY_DODGE, _GetAffectLabel(item.APPLY_DODGE)),
(item.APPLY_ATT_GRADE_BONUS, _GetAffectLabel(item.APPLY_ATT_GRADE_BONUS)),
(item.APPLY_DEF_GRADE_BONUS, _GetAffectLabel(item.APPLY_DEF_GRADE_BONUS)),
(item.APPLY_RESIST_SWORD, _GetAffectLabel(item.APPLY_RESIST_SWORD)),
(item.APPLY_RESIST_TWOHAND, _GetAffectLabel(item.APPLY_RESIST_TWOHAND)),
(item.APPLY_RESIST_DAGGER, _GetAffectLabel(item.APPLY_RESIST_DAGGER)),
(item.APPLY_RESIST_BOW, _GetAffectLabel(item.APPLY_RESIST_BOW)),
(item.APPLY_RESIST_FIRE, _GetAffectLabel(item.APPLY_RESIST_FIRE)),
(item.APPLY_RESIST_ELEC, _GetAffectLabel(item.APPLY_RESIST_ELEC)),
(item.APPLY_RESIST_MAGIC, _GetAffectLabel(item.APPLY_RESIST_MAGIC)),
(item.APPLY_RESIST_WIND, _GetAffectLabel(item.APPLY_RESIST_WIND)),
(item.APPLY_RESIST_ICE, _GetAffectLabel(item.APPLY_RESIST_ICE)),
(item.APPLY_RESIST_EARTH, _GetAffectLabel(item.APPLY_RESIST_EARTH)),
(item.APPLY_RESIST_DARK, _GetAffectLabel(item.APPLY_RESIST_DARK)),
)
SPEED_OPTIONS = (
(0, "Slow / 3s"),
(1, "Normal / 2s"),
(2, "Fast / 1s"),
)
def SetSwitchbotState(speedIndex, activeCount, scrollCount, etaSeconds):
global STATE_SPEED
global STATE_ACTIVE_COUNT
global STATE_SCROLL_COUNT
global STATE_ETA_SECONDS
STATE_SPEED = int(speedIndex)
STATE_ACTIVE_COUNT = max(0, int(activeCount))
STATE_SCROLL_COUNT = max(0, int(scrollCount))
STATE_ETA_SECONDS = max(0, int(etaSeconds))
for window in OPEN_WINDOWS:
try:
window.Refresh()
except:
pass
def SetSwitchbotSlotState(slotIndex, active, itemCell, attrType, minValue, attempts):
slotIndex = int(slotIndex)
if slotIndex < 0 or slotIndex >= len(STATE_SLOTS):
return
STATE_SLOTS[slotIndex]["active"] = 1 if int(active) else 0
STATE_SLOTS[slotIndex]["itemCell"] = int(itemCell)
STATE_SLOTS[slotIndex]["attrType"] = int(attrType)
STATE_SLOTS[slotIndex]["minValue"] = int(minValue)
STATE_SLOTS[slotIndex]["attempts"] = int(attempts)
for window in OPEN_WINDOWS:
try:
window.Refresh()
except:
pass
class SwitchbotWindow(ui.BoardWithTitleBar):
CANDIDATE_PAGE_SIZE = 12
def __init__(self):
ui.BoardWithTitleBar.__init__(self)
OPEN_WINDOWS.append(self)
self.selectedSlotIndex = 0
self.pageIndex = 0
self.candidateSlots = []
self.slotWidgets = []
self.candidateButtons = []
self.speedLabels = {}
self.attrLabels = {}
self.AddFlag("float")
self.AddFlag("movable")
self.SetSize(535, 406)
self.SetTitleName("Switchbot")
self.SetCloseEvent(self.Hide)
self.__CreateChildren()
self.Hide()
def __del__(self):
if self in OPEN_WINDOWS:
OPEN_WINDOWS.remove(self)
ui.BoardWithTitleBar.__del__(self)
def Destroy(self):
if self in OPEN_WINDOWS:
OPEN_WINDOWS.remove(self)
self.ClearDictionary()
self.slotWidgets = []
self.candidateButtons = []
self.candidateSlots = []
self.speedLabels = {}
self.attrLabels = {}
def __CreateChildren(self):
self.statusLine = self.__CreateLine(15, 36)
self.scrollLine = self.__CreateLine(15, 56)
self.etaLine = self.__CreateLine(15, 76)
self.helpLine = self.__CreateLine(15, 96)
self.speedCombo = ui.ComboBox()
self.speedCombo.SetParent(self)
self.speedCombo.SetPosition(398, 34)
self.speedCombo.SetSize(120, 18)
self.speedCombo.SetEvent(self.__OnChangeSpeed)
self.speedCombo.Show()
for (speedIndex, label) in SPEED_OPTIONS:
self.speedCombo.InsertItem(speedIndex, label)
self.speedLabels[speedIndex] = label
for slotIndex in range(5):
baseY = 126 + slotIndex * 38
row = {}
row["selectButton"] = self.__CreateButton(15, baseY, 48, "Slot %d" % (slotIndex + 1))
row["selectButton"].SetEvent(self.__SelectSlot, slotIndex)
row["itemLine"] = self.__CreateLine(72, baseY + 2)
row["statusLine"] = self.__CreateLine(72, baseY + 18)
row["attrCombo"] = ui.ComboBox()
row["attrCombo"].SetParent(self)
row["attrCombo"].SetPosition(220, baseY)
row["attrCombo"].SetSize(165, 18)
row["attrCombo"].SetEvent(lambda attrType, line=slotIndex: self.__SetAttrType(line, attrType))
row["attrCombo"].Show()
for (attrType, label) in ATTR_OPTIONS:
row["attrCombo"].InsertItem(attrType, label)
attrBar = ui.SlotBar()
attrBar.SetParent(self)
attrBar.SetPosition(392, baseY)
attrBar.SetSize(48, 18)
attrBar.Show()
row["valueBar"] = attrBar
row["valueEdit"] = ui.EditLine()
row["valueEdit"].SetParent(attrBar)
row["valueEdit"].SetPosition(3, 3)
row["valueEdit"].SetSize(42, 12)
row["valueEdit"].SetMax(5)
row["valueEdit"].SetNumberMode()
row["valueEdit"].Show()
row["startButton"] = self.__CreateButton(447, baseY, 40, "Start")
row["startButton"].SetEvent(self.__ToggleSlot, slotIndex)
row["clearButton"] = self.__CreateButton(492, baseY, 28, "X")
row["clearButton"].SetEvent(self.__ClearSlot, slotIndex)
self.slotWidgets.append(row)
self.prevButton = self.__CreateButton(15, 330, 44, "Prev")
self.prevButton.SetEvent(self.__ChangePage, -1)
self.pageLine = self.__CreateLine(80, 334)
self.nextButton = self.__CreateButton(170, 330, 44, "Next")
self.nextButton.SetEvent(self.__ChangePage, 1)
for index in range(self.CANDIDATE_PAGE_SIZE):
column = index % 2
row = index // 2
button = self.__CreateButton(15 + column * 155, 356 + row * 18, 148, "-")
button.SetEvent(self.__AssignCandidate, index)
self.candidateButtons.append(button)
self.syncButton = self.__CreateButton(305, 330, 54, "Sync")
self.syncButton.SetEvent(self.__Sync)
self.startAllButton = self.__CreateButton(366, 330, 74, "Start All")
self.startAllButton.SetEvent(self.__StartAll)
self.stopAllButton = self.__CreateButton(446, 330, 74, "Stop All")
self.stopAllButton.SetEvent(self.__StopAll)
def __CreateLine(self, x, y):
textLine = ui.TextLine()
textLine.SetParent(self)
textLine.SetPosition(x, y)
textLine.SetOutline()
textLine.Show()
return textLine
def __CreateButton(self, x, y, width, text):
button = ui.Button()
button.SetParent(self)
button.SetPosition(x, y)
button.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
button.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
button.SetDisableVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetSize(width, 17)
button.SetText(text)
button.Show()
return button
def __Send(self, command):
net.SendChatPacket(command, 0)
def __IsEligibleItem(self, slotPos):
itemVnum = player.GetItemIndex(slotPos)
if itemVnum == 0:
return False
item.SelectItem(itemVnum)
if item.GetItemType() == item.ITEM_TYPE_COSTUME:
return False
if item.GetItemType() not in (item.ITEM_TYPE_WEAPON, item.ITEM_TYPE_ARMOR):
return False
for attrIndex in range(player.ATTRIBUTE_SLOT_MAX_NUM):
if player.GetItemAttribute(slotPos, attrIndex)[0] != 0:
return True
return False
def __GetItemLabel(self, slotPos):
itemVnum = player.GetItemIndex(slotPos)
if itemVnum == 0:
return "Slot %d" % slotPos
item.SelectItem(itemVnum)
return "%d: %s" % (slotPos, item.GetItemName())
def __BuildCandidateSlots(self):
self.candidateSlots = []
for slotPos in range(player.INVENTORY_PAGE_SIZE * player.INVENTORY_PAGE_COUNT):
if self.__IsEligibleItem(slotPos):
self.candidateSlots.append(slotPos)
def __FormatEta(self):
if STATE_ETA_SECONDS <= 0:
return "ETA: -"
minutes = STATE_ETA_SECONDS // 60
seconds = STATE_ETA_SECONDS % 60
return "ETA: %02d:%02d" % (minutes, seconds)
def __GetAttrType(self, slotIndex):
return STATE_SLOTS[slotIndex]["attrType"]
def __GetMinValue(self, slotIndex):
text = self.slotWidgets[slotIndex]["valueEdit"].GetText()
if not text:
return STATE_SLOTS[slotIndex]["minValue"]
return int(text)
def __SelectSlot(self, slotIndex):
self.selectedSlotIndex = slotIndex
self.Refresh()
def __SetAttrType(self, slotIndex, attrType):
SetSwitchbotSlotState(
slotIndex,
STATE_SLOTS[slotIndex]["active"],
STATE_SLOTS[slotIndex]["itemCell"],
attrType,
STATE_SLOTS[slotIndex]["minValue"],
STATE_SLOTS[slotIndex]["attempts"],
)
def __AssignCandidate(self, localIndex):
candidateIndex = self.pageIndex * self.CANDIDATE_PAGE_SIZE + localIndex
if candidateIndex < 0 or candidateIndex >= len(self.candidateSlots):
return
slotIndex = self.selectedSlotIndex
SetSwitchbotSlotState(
slotIndex,
0,
self.candidateSlots[candidateIndex],
STATE_SLOTS[slotIndex]["attrType"],
STATE_SLOTS[slotIndex]["minValue"],
0,
)
def __ToggleSlot(self, slotIndex):
slotState = STATE_SLOTS[slotIndex]
if slotState["active"]:
self.__Send("/switchbot stop %d" % slotIndex)
return
itemCell = slotState["itemCell"]
attrType = self.__GetAttrType(slotIndex)
minValue = self.__GetMinValue(slotIndex)
if not self.__IsEligibleItem(itemCell) or attrType <= 0 or minValue <= 0:
return
SetSwitchbotSlotState(slotIndex, 0, itemCell, attrType, minValue, slotState["attempts"])
self.__Send("/switchbot start %d %d %d %d" % (slotIndex, itemCell, attrType, minValue))
def __ClearSlot(self, slotIndex):
self.__Send("/switchbot clear %d" % slotIndex)
def __ChangePage(self, delta):
totalPages = max(1, (len(self.candidateSlots) + self.CANDIDATE_PAGE_SIZE - 1) // self.CANDIDATE_PAGE_SIZE)
self.pageIndex = max(0, min(totalPages - 1, self.pageIndex + delta))
self.Refresh()
def __OnChangeSpeed(self, speedIndex):
self.__Send("/switchbot speed %d" % int(speedIndex))
def __Sync(self):
self.__Send("/switchbot sync")
def __StartAll(self):
for slotIndex in range(len(STATE_SLOTS)):
slotState = STATE_SLOTS[slotIndex]
if slotState["active"]:
continue
itemCell = slotState["itemCell"]
attrType = self.__GetAttrType(slotIndex)
minValue = self.__GetMinValue(slotIndex)
if not self.__IsEligibleItem(itemCell) or attrType <= 0 or minValue <= 0:
continue
self.__Send("/switchbot start %d %d %d %d" % (slotIndex, itemCell, attrType, minValue))
def __StopAll(self):
self.__Send("/switchbot stop_all")
def Refresh(self):
self.__BuildCandidateSlots()
self.statusLine.SetText("Status: %d active slot(s)" % STATE_ACTIVE_COUNT)
self.scrollLine.SetText("Switch items: %d" % STATE_SCROLL_COUNT)
self.etaLine.SetText(self.__FormatEta())
self.helpLine.SetText("Select a row, assign an item, pick a target bonus and min value")
self.speedCombo.SetCurrentItem(self.speedLabels.get(STATE_SPEED, SPEED_OPTIONS[1][1]))
for slotIndex in range(len(self.slotWidgets)):
slotState = STATE_SLOTS[slotIndex]
row = self.slotWidgets[slotIndex]
row["selectButton"].SetText(("> " if self.selectedSlotIndex == slotIndex else "") + "S%d" % (slotIndex + 1))
row["itemLine"].SetText("Item: %s" % ("-" if not self.__IsEligibleItem(slotState["itemCell"]) else self.__GetItemLabel(slotState["itemCell"])))
row["statusLine"].SetText("Target: %s / tries %d / %s" % (
_GetAffectLabel(slotState["attrType"]),
slotState["attempts"],
"Running" if slotState["active"] else "Ready",
))
if slotState["attrType"] > 0:
row["attrCombo"].SetCurrentItem(_GetAffectLabel(slotState["attrType"]))
else:
row["attrCombo"].SetCurrentItem("-")
if slotState["minValue"] > 0 and row["valueEdit"].GetText() != str(slotState["minValue"]):
row["valueEdit"].SetText(str(slotState["minValue"]))
elif slotState["minValue"] <= 0 and row["valueEdit"].GetText():
row["valueEdit"].SetText("")
row["startButton"].SetText("Stop" if slotState["active"] else "Start")
start = self.pageIndex * self.CANDIDATE_PAGE_SIZE
end = start + self.CANDIDATE_PAGE_SIZE
pageSlots = self.candidateSlots[start:end]
totalPages = max(1, (len(self.candidateSlots) + self.CANDIDATE_PAGE_SIZE - 1) // self.CANDIDATE_PAGE_SIZE)
self.pageLine.SetText("Candidates %d / %d" % (self.pageIndex + 1, totalPages))
for localIndex in range(len(self.candidateButtons)):
button = self.candidateButtons[localIndex]
if localIndex < len(pageSlots):
button.SetText(self.__GetItemLabel(pageSlots[localIndex]))
button.Enable()
else:
button.SetText("-")
button.Disable()
if self.pageIndex > 0:
self.prevButton.Enable()
else:
self.prevButton.Disable()
if end < len(self.candidateSlots):
self.nextButton.Enable()
else:
self.nextButton.Disable()
def Show(self):
self.__Sync()
self.Refresh()
ui.BoardWithTitleBar.Show(self)

173
assets/root/uiTeleport.py Normal file
View File

@@ -0,0 +1,173 @@
import time
import background
import net
import player
import quest
import ui
class TeleportWindow(ui.BoardWithTitleBar):
TITLE = "Teleport System"
PRESET_BUTTONS = (
(1, "Village 1"),
(2, "Village 2"),
(3, "Valley"),
(4, "Desert"),
(5, "Sohan"),
(6, "Fireland"),
(7, "Devil"),
(8, "Cave"),
)
def __init__(self):
ui.BoardWithTitleBar.__init__(self)
self.lastRefreshTime = 0.0
self.slotRows = []
self.AddFlag("float")
self.AddFlag("movable")
self.SetSize(310, 255)
self.SetTitleName("Teleport")
self.SetCloseEvent(self.Hide)
self.__CreateChildren()
self.Hide()
def __del__(self):
ui.BoardWithTitleBar.__del__(self)
def __CreateChildren(self):
self.statusLine = self.__CreateValueLine(15, 36)
self.mapLine = self.__CreateValueLine(15, 56)
self.coordLine = self.__CreateValueLine(15, 76)
self.slotSummaryLine = self.__CreateValueLine(15, 96)
self.cooldownLine = self.__CreateValueLine(15, 116)
for index, presetData in enumerate(self.PRESET_BUTTONS):
(presetId, label) = presetData
row = index // 4
column = index % 4
button = self.__CreateButton(15 + column * 71, 142 + row * 24, 64, label)
button.SetEvent(self.__SendPresetWarp, presetId)
for slot in range(1, 6):
label = self.__CreateValueLine(15, 196 + (slot - 1) * 11)
saveButton = self.__CreateButton(100, 192 + (slot - 1) * 11, 42, "Save")
saveButton.SetEvent(self.__SendSaveSlot, slot)
useButton = self.__CreateButton(148, 192 + (slot - 1) * 11, 42, "Use")
useButton.SetEvent(self.__SendUseSlot, slot)
self.slotRows.append((label, saveButton, useButton))
def __CreateValueLine(self, x, y):
textLine = ui.TextLine()
textLine.SetParent(self)
textLine.SetPosition(x, y)
textLine.SetOutline()
textLine.Show()
return textLine
def __CreateButton(self, x, y, width, text):
button = ui.Button()
button.SetParent(self)
button.SetPosition(x, y)
button.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
button.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
button.SetDisableVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
button.SetSize(width, 17)
button.SetText(text)
button.Show()
return button
def __GetTeleportQuestData(self):
questCount = min(quest.GetQuestCount(), quest.QUEST_MAX_NUM)
for questIndex in range(questCount):
(questName, questIcon, questCounterName, questCounterValue) = quest.GetQuestData(questIndex)
if questName != self.TITLE:
continue
maxSlots = 3
if "/" in questCounterName:
parts = questCounterName.split("/")
try:
maxSlots = int(parts[-1].strip())
except:
maxSlots = 3
(clockName, clockValue) = quest.GetQuestLastTime(questIndex)
return {
"savedCount" : max(questCounterValue, 0),
"maxSlots" : max(maxSlots, 3),
"clockName" : clockName,
"clockValue" : max(clockValue, 0),
}
return None
def __FormatCooldown(self, seconds):
return "%02d:%02d" % (seconds // 60, seconds % 60)
def __RefreshSlotRows(self, maxSlots):
for slot, row in enumerate(self.slotRows, 1):
(label, saveButton, useButton) = row
if slot <= maxSlots:
label.SetText("Slot %d" % slot)
saveButton.Enable()
useButton.Enable()
else:
label.SetText("Slot %d (VIP)" % slot)
saveButton.Disable()
useButton.Disable()
def Refresh(self):
teleportData = self.__GetTeleportQuestData()
(x, y, z) = player.GetMainCharacterPosition()
currentMap = background.GetCurrentMapName()
self.mapLine.SetText("Map: %s" % currentMap)
self.coordLine.SetText("Coords: %d, %d" % (x // 100, y // 100))
if not teleportData:
self.statusLine.SetText("Status: Waiting for teleport quest")
self.slotSummaryLine.SetText("Saved: -")
self.cooldownLine.SetText("Cooldown: -")
self.__RefreshSlotRows(3)
return
self.statusLine.SetText("Status: %s" % ("Cooldown" if teleportData["clockValue"] > 0 else "Ready"))
self.slotSummaryLine.SetText("Saved: %d / %d" % (teleportData["savedCount"], teleportData["maxSlots"]))
if teleportData["clockValue"] > 0 and len(teleportData["clockName"]) > 0:
self.cooldownLine.SetText("Cooldown: %s" % self.__FormatCooldown(teleportData["clockValue"]))
else:
self.cooldownLine.SetText("Cooldown: Ready")
self.__RefreshSlotRows(teleportData["maxSlots"])
def Show(self):
self.Refresh()
ui.BoardWithTitleBar.Show(self)
def OnUpdate(self):
currentTime = time.time()
if currentTime - self.lastRefreshTime < 0.2:
return
self.lastRefreshTime = currentTime
self.Refresh()
def __SendTeleportCommand(self, action, arg):
net.SendChatPacket("/teleport_system %s %d" % (action, arg), 0)
self.lastRefreshTime = 0.0
self.Refresh()
def __SendSaveSlot(self, slot):
self.__SendTeleportCommand("save", slot)
def __SendUseSlot(self, slot):
self.__SendTeleportCommand("saved", slot)
def __SendPresetWarp(self, presetId):
self.__SendTeleportCommand("preset", presetId)

View File

@@ -8,6 +8,7 @@ import localeInfo
import constInfo
import chrmgr
import player
import uiAutopickup
import uiPrivateShopBuilder # 占쏙옙占쏙옙호
import interfaceModule # 占쏙옙占쏙옙호
@@ -39,8 +40,14 @@ class OptionDialog(ui.ScriptWindow):
self.alwaysShowNameButtonList = []
self.showDamageButtonList = []
self.showsalesTextButtonList = []
self.autoPickupButton = None
self.autoPickupDialog = None
def Destroy(self):
if self.autoPickupDialog:
self.autoPickupDialog.Destroy()
self.autoPickupDialog = None
self.ClearDictionary()
self.__Initialize()
@@ -80,6 +87,7 @@ class OptionDialog(ui.ScriptWindow):
self.showDamageButtonList.append(GetObject("show_damage_off_button"))
self.showsalesTextButtonList.append(GetObject("salestext_on_button"))
self.showsalesTextButtonList.append(GetObject("salestext_off_button"))
self.autoPickupButton = GetObject("autopickup_button")
except:
import exception
@@ -130,6 +138,7 @@ class OptionDialog(ui.ScriptWindow):
self.showsalesTextButtonList[0].SAFE_SetEvent(self.__OnClickSalesTextOnButton)
self.showsalesTextButtonList[1].SAFE_SetEvent(self.__OnClickSalesTextOffButton)
self.autoPickupButton.SAFE_SetEvent(self.__OnClickAutoPickupButton)
self.__ClickRadioButton(self.nameColorModeButtonList, constInfo.GET_CHRNAME_COLOR_INDEX())
self.__ClickRadioButton(self.viewTargetBoardButtonList, constInfo.GET_VIEW_OTHER_EMPIRE_PLAYER_TARGET_BOARD())
@@ -241,6 +250,15 @@ class OptionDialog(ui.ScriptWindow):
def __OnClickSalesTextOffButton(self):
systemSetting.SetShowSalesTextFlag(False)
self.RefreshShowSalesText()
def __OnClickAutoPickupButton(self):
if not self.autoPickupDialog:
self.autoPickupDialog = uiAutopickup.AutoPickupWindow()
if self.autoPickupDialog.IsShow():
self.autoPickupDialog.Hide()
else:
self.autoPickupDialog.Show()
def __CheckPvPProtectedLevelPlayer(self):
if player.GetStatus(player.LEVEL)<constInfo.PVPMODE_PROTECTED_LEVEL:

View File

@@ -11,6 +11,7 @@ import grp
import uiScriptLocale
import uiRefine
import uiAttachMetin
import uiStoneQueue
import uiPickMoney
import uiCommon
import uiPrivateShopBuilder # Prevent ItemMove while private shop is open
@@ -23,6 +24,12 @@ ITEM_FLAG_APPLICABLE = 1 << 14
class CostumeWindow(ui.ScriptWindow):
SLOT_ORDER = (
item.COSTUME_SLOT_BODY,
item.COSTUME_SLOT_HAIR,
item.COSTUME_SLOT_SASH,
)
def __init__(self, wndInventory):
import exception
@@ -87,8 +94,7 @@ class CostumeWindow(ui.ScriptWindow):
def RefreshCostumeSlot(self):
getItemVNum=player.GetItemIndex
for i in range(item.COSTUME_SLOT_COUNT):
slotNumber = item.COSTUME_SLOT_START + i
for slotNumber in self.SLOT_ORDER:
self.wndEquip.SetItemSlot(slotNumber, getItemVNum(slotNumber), 0)
self.wndEquip.RefreshSlot()
@@ -251,11 +257,13 @@ class InventoryWindow(ui.ScriptWindow):
isLoaded = 0
isOpenedCostumeWindowWhenClosingInventory = 0 # Whether costume window was open when closing inventory
isOpenedBeltWindowWhenClosingInventory = 0 # Whether belt inventory was open when closing inventory
sashAbsorbSlot = -1
def __init__(self):
ui.ScriptWindow.__init__(self)
self.isOpenedBeltWindowWhenClosingInventory = 0 # Whether belt inventory was open when closing inventory
self.sashAbsorbSlot = -1
self.__LoadWindow()
@@ -357,6 +365,10 @@ class InventoryWindow(ui.ScriptWindow):
self.attachMetinDialog = uiAttachMetin.AttachMetinDialog()
self.attachMetinDialog.Hide()
## StoneQueueDialog
self.stoneQueueDialog = uiStoneQueue.StoneQueueWindow()
self.stoneQueueDialog.Hide()
## MoneySlot
self.wndMoneySlot.SetEvent(ui.__mem_func__(self.OpenPickMoneyDialog))
@@ -407,6 +419,9 @@ class InventoryWindow(ui.ScriptWindow):
self.attachMetinDialog.Destroy()
self.attachMetinDialog = 0
self.stoneQueueDialog.Destroy()
self.stoneQueueDialog = 0
self.tooltipItem = None
self.wndItem = 0
self.wndEquip = 0
@@ -431,6 +446,7 @@ class InventoryWindow(ui.ScriptWindow):
self.equipmentTab = []
def Hide(self):
self.__AbortSashAbsorb(False)
if constInfo.GET_ITEM_QUESTION_DIALOG_STATUS():
self.OnCloseQuestionDialog()
return
@@ -455,6 +471,91 @@ class InventoryWindow(ui.ScriptWindow):
def Close(self):
self.Hide()
def __AbortSashAbsorb(self, showMessage=True):
if self.sashAbsorbSlot == -1:
return
self.sashAbsorbSlot = -1
if showMessage:
chat.AppendChat(chat.CHAT_TYPE_INFO, "Sash absorb canceled.")
def __IsSashItem(self, slotIndex):
itemVnum = player.GetItemIndex(slotIndex)
if not itemVnum:
return False
item.SelectItem(itemVnum)
return item.GetItemType() == item.ITEM_TYPE_COSTUME and item.GetItemSubType() == item.COSTUME_TYPE_SASH
def __CanAbsorbIntoSash(self, sashSlot, targetSlot):
if sashSlot == targetSlot:
return False
if player.IsEquipmentSlot(sashSlot) or player.IsEquipmentSlot(targetSlot):
return False
if player.GetItemMetinSocket(sashSlot, 0):
return False
targetVnum = player.GetItemIndex(targetSlot)
if not targetVnum:
return False
item.SelectItem(targetVnum)
itemType = item.GetItemType()
itemSubType = item.GetItemSubType()
if itemType == item.ITEM_TYPE_WEAPON:
return itemSubType != item.WEAPON_ARROW
if itemType != item.ITEM_TYPE_ARMOR:
return False
return itemSubType in (
item.ARMOR_BODY,
item.ARMOR_HEAD,
item.ARMOR_SHIELD,
item.ARMOR_WRIST,
item.ARMOR_FOOTS,
item.ARMOR_NECK,
item.ARMOR_EAR,
)
def __BeginSashAbsorb(self, sashSlot):
if player.IsEquipmentSlot(sashSlot):
chat.AppendChat(chat.CHAT_TYPE_INFO, "Unequip the sash before absorbing bonuses.")
return
if player.GetItemMetinSocket(sashSlot, 0):
chat.AppendChat(chat.CHAT_TYPE_INFO, "This sash already contains absorbed bonuses.")
return
if self.sashAbsorbSlot == sashSlot:
self.__AbortSashAbsorb()
return
self.sashAbsorbSlot = sashSlot
chat.AppendChat(chat.CHAT_TYPE_INFO, "Select a weapon or armor piece to absorb into the sash.")
def __OpenSashAbsorbQuestion(self, sashSlot, targetSlot):
targetVnum = player.GetItemIndex(targetSlot)
item.SelectItem(targetVnum)
targetName = item.GetItemName()
self.questionDialog = uiCommon.QuestionDialog()
self.questionDialog.SetText("Absorb bonuses from %s? The source item will be destroyed." % targetName)
self.questionDialog.SetAcceptEvent(ui.__mem_func__(self.__AcceptSashAbsorb))
self.questionDialog.SetCancelEvent(ui.__mem_func__(self.OnCloseQuestionDialog))
self.questionDialog.sashSlot = sashSlot
self.questionDialog.targetSlot = targetSlot
self.questionDialog.Open()
constInfo.SET_ITEM_QUESTION_DIALOG_STATUS(1)
def __AcceptSashAbsorb(self):
net.SendChatPacket("/sash absorb %d %d" % (self.questionDialog.sashSlot, self.questionDialog.targetSlot), 0)
self.__AbortSashAbsorb(False)
self.OnCloseQuestionDialog()
def SetInventoryPage(self, page):
self.inventoryPageIndex = page
self.inventoryTab[1-page].SetUp()
@@ -701,6 +802,18 @@ class InventoryWindow(ui.ScriptWindow):
itemSlotIndex = self.__InventoryLocalSlotPosToGlobalSlotPos(itemSlotIndex)
if self.sashAbsorbSlot != -1 and not mouseModule.mouseController.isAttached():
if itemSlotIndex == self.sashAbsorbSlot:
self.__AbortSashAbsorb()
return
if self.__CanAbsorbIntoSash(self.sashAbsorbSlot, itemSlotIndex):
self.__OpenSashAbsorbQuestion(self.sashAbsorbSlot, itemSlotIndex)
else:
chat.AppendChat(chat.CHAT_TYPE_INFO, "Only unequipped weapon or armor items can be absorbed.")
self.__AbortSashAbsorb(False)
return
if mouseModule.mouseController.isAttached():
attachedSlotType = mouseModule.mouseController.GetAttachedType()
attachedSlotPos = mouseModule.mouseController.GetAttachedSlotNumber()
@@ -841,20 +954,6 @@ class InventoryWindow(ui.ScriptWindow):
scrollIndex = player.GetItemIndex(scrollSlotPos)
targetIndex = player.GetItemIndex(targetSlotPos)
if player.REFINE_OK != player.CanRefine(scrollIndex, targetSlotPos):
return
###########################################################
self.__SendUseItemToItemPacket(scrollSlotPos, targetSlotPos)
#net.SendItemUseToItemPacket(scrollSlotPos, targetSlotPos)
return
###########################################################
###########################################################
#net.SendRequestRefineInfoPacket(targetSlotPos)
#return
###########################################################
result = player.CanRefine(scrollIndex, targetSlotPos)
if player.REFINE_ALREADY_MAX_SOCKET_COUNT == result:
@@ -879,7 +978,12 @@ class InventoryWindow(ui.ScriptWindow):
if player.REFINE_OK != result:
return
self.refineDialog.Open(scrollSlotPos, targetSlotPos)
item.SelectItem(targetIndex)
if item.GetItemType() == item.ITEM_TYPE_METIN:
self.stoneQueueDialog.Open(scrollSlotPos, targetSlotPos)
return
self.__SendUseItemToItemPacket(scrollSlotPos, targetSlotPos)
def DetachMetinFromItem(self, scrollSlotPos, targetSlotPos):
scrollIndex = player.GetItemIndex(scrollSlotPos)
@@ -1206,6 +1310,11 @@ class InventoryWindow(ui.ScriptWindow):
def __UseItem(self, slotIndex):
ItemVNum = player.GetItemIndex(slotIndex)
item.SelectItem(ItemVNum)
if self.__IsSashItem(slotIndex):
self.__BeginSashAbsorb(slotIndex)
return
if item.IsFlag(item.ITEM_FLAG_CONFIRM_WHEN_USE):
self.questionDialog = uiCommon.QuestionDialog()
self.questionDialog.SetText(localeInfo.INVENTORY_REALLY_USE_ITEM)

View File

@@ -222,6 +222,15 @@ class MiniMap(ui.ScriptWindow):
self.tooltipAtlasOpen = MapTextToolTip()
self.tooltipAtlasOpen.SetText(localeInfo.MINIMAP_SHOW_AREAMAP)
self.tooltipAtlasOpen.Show()
self.tooltipBiolog = MapTextToolTip()
self.tooltipBiolog.SetText("Biolog")
self.tooltipBiolog.Show()
self.tooltipTeleport = MapTextToolTip()
self.tooltipTeleport.SetText("Teleport")
self.tooltipTeleport.Show()
self.tooltipSwitchbot = MapTextToolTip()
self.tooltipSwitchbot.SetText("Switchbot")
self.tooltipSwitchbot.Show()
self.tooltipInfo = MapTextToolTip()
self.tooltipInfo.Show()
@@ -259,12 +268,21 @@ class MiniMap(ui.ScriptWindow):
self.MiniMapHideButton = 0
self.MiniMapShowButton = 0
self.AtlasShowButton = 0
self.BiologButton = 0
self.TeleportButton = 0
self.SwitchbotButton = 0
self.biologButtonEvent = None
self.teleportButtonEvent = None
self.switchbotButtonEvent = None
self.tooltipMiniMapOpen = 0
self.tooltipMiniMapClose = 0
self.tooltipScaleUp = 0
self.tooltipScaleDown = 0
self.tooltipAtlasOpen = 0
self.tooltipBiolog = 0
self.tooltipTeleport = 0
self.tooltipSwitchbot = 0
self.tooltipInfo = None
self.serverInfo = None
@@ -346,6 +364,39 @@ class MiniMap(ui.ScriptWindow):
if miniMap.IsAtlas():
self.AtlasShowButton.SetEvent(ui.__mem_func__(self.ShowAtlas))
self.BiologButton = ui.Button()
self.BiologButton.SetParent(self.OpenWindow)
self.BiologButton.SetPosition(9, 111)
self.BiologButton.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
self.BiologButton.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
self.BiologButton.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
self.BiologButton.SetText("Bio")
if self.biologButtonEvent:
self.BiologButton.SetEvent(self.biologButtonEvent)
self.BiologButton.Show()
self.TeleportButton = ui.Button()
self.TeleportButton.SetParent(self.OpenWindow)
self.TeleportButton.SetPosition(9, 132)
self.TeleportButton.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
self.TeleportButton.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
self.TeleportButton.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
self.TeleportButton.SetText("TP")
if self.teleportButtonEvent:
self.TeleportButton.SetEvent(self.teleportButtonEvent)
self.TeleportButton.Show()
self.SwitchbotButton = ui.Button()
self.SwitchbotButton.SetParent(self.OpenWindow)
self.SwitchbotButton.SetPosition(9, 153)
self.SwitchbotButton.SetUpVisual("d:/ymir work/ui/public/small_thin_button_01.sub")
self.SwitchbotButton.SetOverVisual("d:/ymir work/ui/public/small_thin_button_02.sub")
self.SwitchbotButton.SetDownVisual("d:/ymir work/ui/public/small_thin_button_03.sub")
self.SwitchbotButton.SetText("Sw")
if self.switchbotButtonEvent:
self.SwitchbotButton.SetEvent(self.switchbotButtonEvent)
self.SwitchbotButton.Show()
(ButtonPosX, ButtonPosY) = self.MiniMapShowButton.GetGlobalPosition()
self.tooltipMiniMapOpen.SetTooltipPosition(ButtonPosX, ButtonPosY)
@@ -361,6 +412,15 @@ class MiniMap(ui.ScriptWindow):
(ButtonPosX, ButtonPosY) = self.AtlasShowButton.GetGlobalPosition()
self.tooltipAtlasOpen.SetTooltipPosition(ButtonPosX, ButtonPosY)
(ButtonPosX, ButtonPosY) = self.BiologButton.GetGlobalPosition()
self.tooltipBiolog.SetTooltipPosition(ButtonPosX, ButtonPosY)
(ButtonPosX, ButtonPosY) = self.TeleportButton.GetGlobalPosition()
self.tooltipTeleport.SetTooltipPosition(ButtonPosX, ButtonPosY)
(ButtonPosX, ButtonPosY) = self.SwitchbotButton.GetGlobalPosition()
self.tooltipSwitchbot.SetTooltipPosition(ButtonPosX, ButtonPosY)
self.ShowMiniMap()
def Destroy(self):
@@ -441,6 +501,21 @@ class MiniMap(ui.ScriptWindow):
else:
self.tooltipAtlasOpen.Hide()
if True == self.BiologButton.IsIn():
self.tooltipBiolog.Show()
else:
self.tooltipBiolog.Hide()
if True == self.TeleportButton.IsIn():
self.tooltipTeleport.Show()
else:
self.tooltipTeleport.Hide()
if True == self.SwitchbotButton.IsIn():
self.tooltipSwitchbot.Show()
else:
self.tooltipSwitchbot.Hide()
def OnRender(self):
(x, y) = self.GetGlobalPosition()
fx = float(x)
@@ -485,3 +560,18 @@ class MiniMap(ui.ScriptWindow):
self.AtlasWindow.Hide()
else:
self.AtlasWindow.Show()
def SetBiologButtonEvent(self, event):
self.biologButtonEvent = event
if self.BiologButton:
self.BiologButton.SetEvent(event)
def SetTeleportButtonEvent(self, event):
self.teleportButtonEvent = event
if self.TeleportButton:
self.TeleportButton.SetEvent(event)
def SetSwitchbotButtonEvent(self, event):
self.switchbotButtonEvent = event
if self.SwitchbotButton:
self.SwitchbotButton.SetEvent(event)

View File

@@ -22,7 +22,7 @@ class PlayerGauge(ui.Gauge):
self.SetPosition(-100, -100)
ui.Gauge.Hide(self)
def OnUpdate(self):
def __UpdateScreenPosition(self):
playerIndex = player.GetMainCharacterIndex()
(x, y, z)=textTail.GetPosition(playerIndex)
@@ -30,6 +30,14 @@ class PlayerGauge(ui.Gauge):
isChat = textTail.IsChat(playerIndex)
ui.Gauge.SetPosition(self, int(x - self.GetWidth() // 2), int(y + 5) + isChat * 17)
def OnUpdate(self):
self.__UpdateScreenPosition()
def OnRender(self):
# Refresh the anchor every render so the gauge tracks interpolated
# character/text-tail positions on high refresh displays.
self.__UpdateScreenPosition()
def RefreshGauge(self):
self.curHP = player.GetStatus(player.HP)

View File

@@ -94,24 +94,32 @@ class PrivateShopAdvertisementBoard(ui.ThinBoard):
net.SendOnClickPacket(self.vid)
return True
def OnUpdate(self):
def __UpdateProjectedPosition(self):
if not self.vid:
return
projectVID = None
if systemSetting.IsShowSalesText():
self.Show()
x, y = chr.GetProjectPosition(self.vid, 220)
self.SetPosition(x - self.GetWidth()/2, y - self.GetHeight()/2)
projectVID = self.vid
elif player.GetMainCharacterIndex() == self.vid:
projectVID = player.GetMainCharacterIndex()
if projectVID is None:
self.Hide()
return
self.Show()
x, y = chr.GetProjectPosition(projectVID, 220)
self.SetPosition(x - self.GetWidth()/2, y - self.GetHeight()/2)
else:
for key in list(g_privateShopAdvertisementBoardDict.keys()):
if player.GetMainCharacterIndex() == key: # When the private shop is visible and closed, the player's own shop ID changes.
g_privateShopAdvertisementBoardDict[key].Show()
x, y = chr.GetProjectPosition(player.GetMainCharacterIndex(), 220)
g_privateShopAdvertisementBoardDict[key].SetPosition(x - self.GetWidth()/2, y - self.GetHeight()/2)
else:
g_privateShopAdvertisementBoardDict[key].Hide()
def OnUpdate(self):
self.__UpdateProjectedPosition()
def OnRender(self):
# Keep the board anchored to the interpolated render position instead
# of the legacy fixed update cadence.
self.__UpdateProjectedPosition()
class PrivateShopBuilder(ui.ScriptWindow):

View File

@@ -41,6 +41,9 @@ class OptionDialog(ui.ScriptWindow):
self.cameraModeButtonList = []
self.fogModeButtonList = []
self.tilingModeButtonList = []
self.renderFPSButtonList = []
self.renderFPSValues = [60, 120, 144, 240, 0]
self.performanceHUDButtonList = []
self.ctrlShadowQuality = 0
def Destroy(self):
@@ -72,6 +75,13 @@ class OptionDialog(ui.ScriptWindow):
self.fogModeButtonList.append(GetObject("fog_level2"))
self.tilingModeButtonList.append(GetObject("tiling_cpu"))
self.tilingModeButtonList.append(GetObject("tiling_gpu"))
self.renderFPSButtonList.append(GetObject("render_fps_60"))
self.renderFPSButtonList.append(GetObject("render_fps_120"))
self.renderFPSButtonList.append(GetObject("render_fps_144"))
self.renderFPSButtonList.append(GetObject("render_fps_240"))
self.renderFPSButtonList.append(GetObject("render_fps_max"))
self.performanceHUDButtonList.append(GetObject("performance_hud_off"))
self.performanceHUDButtonList.append(GetObject("performance_hud_on"))
self.tilingApplyButton=GetObject("tiling_apply")
#self.ctrlShadowQuality = GetObject("shadow_bar")
except:
@@ -106,6 +116,13 @@ class OptionDialog(ui.ScriptWindow):
self.tilingModeButtonList[0].SAFE_SetEvent(self.__OnClickTilingModeCPUButton)
self.tilingModeButtonList[1].SAFE_SetEvent(self.__OnClickTilingModeGPUButton)
self.renderFPSButtonList[0].SAFE_SetEvent(self.__OnClickRenderFPS60Button)
self.renderFPSButtonList[1].SAFE_SetEvent(self.__OnClickRenderFPS120Button)
self.renderFPSButtonList[2].SAFE_SetEvent(self.__OnClickRenderFPS144Button)
self.renderFPSButtonList[3].SAFE_SetEvent(self.__OnClickRenderFPS240Button)
self.renderFPSButtonList[4].SAFE_SetEvent(self.__OnClickRenderFPSMaxButton)
self.performanceHUDButtonList[0].SAFE_SetEvent(self.__OnClickPerformanceHUDOffButton)
self.performanceHUDButtonList[1].SAFE_SetEvent(self.__OnClickPerformanceHUDOnButton)
self.tilingApplyButton.SAFE_SetEvent(self.__OnClickTilingApplyButton)
@@ -115,6 +132,7 @@ class OptionDialog(ui.ScriptWindow):
self.__ClickRadioButton(self.fogModeButtonList, systemSetting.GetFogLevel())
# MR-14: -- END OF -- Fog update by Alaric
self.__ClickRadioButton(self.cameraModeButtonList, constInfo.GET_CAMERA_MAX_DISTANCE_INDEX())
self.RefreshRenderSettings()
if musicInfo.fieldMusic==musicInfo.METIN2THEMA:
self.selectMusicFile.SetText(uiSelectMusic.DEFAULT_THEMA)
@@ -179,6 +197,41 @@ class OptionDialog(ui.ScriptWindow):
self.__ClickRadioButton(self.fogModeButtonList, index)
def __ApplyAndSaveConfig(self):
systemSetting.ApplyConfig()
systemSetting.SaveConfig()
def __GetRenderFPSButtonIndex(self):
renderFPS = systemSetting.GetRenderFPS()
if renderFPS <= 0:
return len(self.renderFPSValues) - 1
try:
return self.renderFPSValues.index(renderFPS)
except ValueError:
bestIndex = 0
bestDistance = abs(self.renderFPSValues[0] - renderFPS)
for index in xrange(len(self.renderFPSValues) - 1):
distance = abs(self.renderFPSValues[index] - renderFPS)
if distance < bestDistance:
bestIndex = index
bestDistance = distance
return bestIndex
def RefreshRenderSettings(self):
self.__ClickRadioButton(self.renderFPSButtonList, self.__GetRenderFPSButtonIndex())
self.__ClickRadioButton(self.performanceHUDButtonList, 1 if systemSetting.IsShowPerformanceHUD() else 0)
def __SetRenderFPS(self, fps):
systemSetting.SetRenderFPS(fps)
self.__ApplyAndSaveConfig()
self.RefreshRenderSettings()
def __SetPerformanceHUD(self, isVisible):
systemSetting.SetShowPerformanceHUDFlag(1 if isVisible else 0)
self.__ApplyAndSaveConfig()
self.RefreshRenderSettings()
def __OnClickCameraModeShortButton(self):
self.__SetCameraMode(0)
@@ -194,6 +247,27 @@ class OptionDialog(ui.ScriptWindow):
def __OnClickFogModeLevel2Button(self):
self.__SetFogLevel(2)
def __OnClickRenderFPS60Button(self):
self.__SetRenderFPS(60)
def __OnClickRenderFPS120Button(self):
self.__SetRenderFPS(120)
def __OnClickRenderFPS144Button(self):
self.__SetRenderFPS(144)
def __OnClickRenderFPS240Button(self):
self.__SetRenderFPS(240)
def __OnClickRenderFPSMaxButton(self):
self.__SetRenderFPS(0)
def __OnClickPerformanceHUDOffButton(self):
self.__SetPerformanceHUD(False)
def __OnClickPerformanceHUDOnButton(self):
self.__SetPerformanceHUD(True)
def __OnChangeMusic(self, fileName):
self.selectMusicFile.SetText(fileName[:MUSIC_FILENAME_MAX_LEN])
@@ -239,6 +313,8 @@ class OptionDialog(ui.ScriptWindow):
return True
def Show(self):
self.RefreshRenderSettings()
self.__SetCurTilingMode()
ui.ScriptWindow.Show(self)
def Close(self):

View File

@@ -20,6 +20,14 @@ import constInfo
WARP_SCROLLS = [22011, 22000, 22010]
TALISMAN_ELEMENT_NAMES = {
1: "Fire",
2: "Ice",
3: "Lightning",
4: "Wind",
5: "Earth",
}
# Tooltip max width when descriptions are long
DESC_MAX_WIDTH = 280
@@ -1021,12 +1029,14 @@ class ItemToolTip(ToolTip):
isCostumeItem = 0
isCostumeHair = 0
isCostumeBody = 0
isCostumeSash = 0
if app.ENABLE_COSTUME_SYSTEM:
if item.ITEM_TYPE_COSTUME == itemType:
isCostumeItem = 1
isCostumeHair = item.COSTUME_TYPE_HAIR == itemSubType
isCostumeBody = item.COSTUME_TYPE_BODY == itemSubType
isCostumeSash = item.COSTUME_TYPE_SASH == itemSubType
#dbg.TraceError("IS_COSTUME_ITEM! body(%d) hair(%d)" % (isCostumeBody, isCostumeHair))
@@ -1117,6 +1127,16 @@ class ItemToolTip(ToolTip):
self.__AppendAccessoryMetinSlotInfo(metinSlot, constInfo.GET_BELT_MATERIAL_VNUM(itemVnum))
### Talisman ###
elif 26 == itemType:
self.__AppendLimitInformation()
self.__AppendAffectInformation()
self.__AppendAttributeInformation(attrSlot)
self.AppendWearableInformation()
self.AppendSpace(5)
elementName = TALISMAN_ELEMENT_NAMES.get(item.GetValue(0), "Unknown")
self.AppendTextLine("Element: %s" % elementName, self.NORMAL_COLOR)
## Costume Item ##
elif 0 != isCostumeItem:
self.__AppendLimitInformation()
@@ -1124,6 +1144,15 @@ class ItemToolTip(ToolTip):
self.__AppendAttributeInformation(attrSlot)
self.AppendWearableInformation()
if isCostumeSash:
self.AppendSpace(5)
absorbPct = metinSlot[1] if metinSlot and metinSlot[1] else item.GetValue(0)
self.AppendTextLine("Absorb rate: %d%%" % absorbPct, self.NORMAL_COLOR)
if metinSlot and metinSlot[0]:
self.AppendTextLine("Absorbed item vnum: %d" % metinSlot[0], self.NORMAL_COLOR)
else:
self.AppendTextLine("Right-click the sash, then select an item to absorb.", self.CONDITION_COLOR)
bHasRealtimeFlag = 0
for i in range(item.LIMIT_MAX_NUM):

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
ScriptType CharacterSoundInformation
SoundDataCount 2
SoundData00 0.066000 "sound/pc2/assassin/dualhand_sword/combo7.wav"
SoundData01 0.033000 "sound/common/swing/w_1h_c_2.wav"
SoundData00 0.231000 "sound/pc2/assassin/dualhand_sword/combo4.wav"
SoundData01 0.198000 "sound/common/swing/a_dh_c_4.wav"

View File

@@ -1,8 +1,6 @@
import uiScriptLocale
import item
COSTUME_START_INDEX = item.COSTUME_SLOT_START
window = {
"name" : "CostumeWindow",
@@ -71,9 +69,9 @@ window = {
"height" : 145,
"slot" : (
{"index":COSTUME_START_INDEX+0, "x":61, "y":45, "width":32, "height":64},
{"index":COSTUME_START_INDEX+1, "x":61, "y": 8, "width":32, "height":32},
{"index":COSTUME_START_INDEX+2, "x":5, "y":145, "width":32, "height":32},
{"index":item.COSTUME_SLOT_BODY, "x":61, "y":45, "width":32, "height":64},
{"index":item.COSTUME_SLOT_HAIR, "x":61, "y": 8, "width":32, "height":32},
{"index":item.COSTUME_SLOT_SASH, "x":5, "y":145, "width":32, "height":32},
),
},
),

View File

@@ -69,6 +69,8 @@ window = {
##{"index":22, "x":75, "y":106, "width":32, "height":32},
## 새 벨트
{"index":23, "x":39, "y":106, "width":32, "height":32},
{"index":24, "x":2, "y":106, "width":32, "height":32},
{"index":25, "x":75, "y":106, "width":32, "height":32},
),
},

View File

@@ -20,7 +20,7 @@ window = {
"y" : 0,
"width" : 300,
"height" : 25*11+8,
"height" : 25*12+8,
"children" :
(
@@ -32,7 +32,7 @@ window = {
"y" : 0,
"width" : 300,
"height" : 25*11+8,
"height" : 25*12+8,
"children" :
(
@@ -433,7 +433,29 @@ window = {
"default_image" : ROOT_PATH + "middle_button_01.sub",
"over_image" : ROOT_PATH + "middle_button_02.sub",
"down_image" : ROOT_PATH + "middle_button_03.sub",
},
},
{
"name" : "autopickup_label",
"type" : "text",
"x" : LINE_LABEL_X,
"y" : 265+2,
"text" : "Auto Pickup",
},
{
"name" : "autopickup_button",
"type" : "button",
"x" : LINE_DATA_X,
"y" : 265,
"text" : "Configure",
"default_image" : ROOT_PATH + "middle_button_01.sub",
"over_image" : ROOT_PATH + "middle_button_02.sub",
"down_image" : ROOT_PATH + "middle_button_03.sub",
},
),
},
),

View File

@@ -91,6 +91,7 @@ window = {
# {"index":item.EQUIPMENT_RING2, "x":75, "y":106, "width":32, "height":32},
## »ő ş§Ć®
{"index":item.EQUIPMENT_BELT, "x":39, "y":106, "width":32, "height":32},
{"index":EQUIPMENT_START_INDEX+25, "x":75, "y":106, "width":32, "height":32},
),
},
## Dragon Soul Button

View File

@@ -1,6 +1,11 @@
import uiScriptLocale
ROOT_PATH = "d:/ymir work/ui/public/"
OPTION_RENDER_FPS = getattr(uiScriptLocale, "OPTION_RENDER_FPS", "Render FPS")
OPTION_RENDER_FPS_MAX = getattr(uiScriptLocale, "OPTION_RENDER_FPS_MAX", "MAX")
OPTION_PERFORMANCE_HUD = getattr(uiScriptLocale, "OPTION_PERFORMANCE_HUD", "Perf HUD")
OPTION_ON = getattr(uiScriptLocale, "OPTION_ON", "On")
OPTION_OFF = getattr(uiScriptLocale, "OPTION_OFF", "Off")
TEMPORARY_X = +13
TEXT_TEMPORARY_X = -10
@@ -15,7 +20,7 @@ window = {
"y" : 0,
"width" : 305,
"height" : 255,
"height" : 295,
"children" :
(
@@ -27,7 +32,7 @@ window = {
"y" : 0,
"width" : 305,
"height" : 255,
"height" : 295,
"children" :
(
@@ -261,6 +266,124 @@ window = {
"down_image" : ROOT_PATH + "middle_Button_03.sub",
},
{
"name" : "render_fps_mode",
"type" : "text",
"x" : 18,
"y" : 210 + 2,
"text" : OPTION_RENDER_FPS,
},
{
"name" : "render_fps_60",
"type" : "radio_button",
"x" : 82,
"y" : 210,
"text" : "60",
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
{
"name" : "render_fps_120",
"type" : "radio_button",
"x" : 124,
"y" : 210,
"text" : "120",
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
{
"name" : "render_fps_144",
"type" : "radio_button",
"x" : 166,
"y" : 210,
"text" : "144",
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
{
"name" : "render_fps_240",
"type" : "radio_button",
"x" : 208,
"y" : 210,
"text" : "240",
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
{
"name" : "render_fps_max",
"type" : "radio_button",
"x" : 250,
"y" : 210,
"text" : OPTION_RENDER_FPS_MAX,
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
{
"name" : "performance_hud_mode",
"type" : "text",
"x" : 18,
"y" : 235 + 2,
"text" : OPTION_PERFORMANCE_HUD,
},
{
"name" : "performance_hud_off",
"type" : "radio_button",
"x" : 110,
"y" : 235,
"text" : OPTION_OFF,
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
{
"name" : "performance_hud_on",
"type" : "radio_button",
"x" : 160,
"y" : 235,
"text" : OPTION_ON,
"default_image" : ROOT_PATH + "small_Button_01.sub",
"over_image" : ROOT_PATH + "small_Button_02.sub",
"down_image" : ROOT_PATH + "small_Button_03.sub",
},
## 그림자
# {

View File

@@ -163,23 +163,14 @@ Files under `.updates/` are created by the launcher. The user shouldn't touch th
1. On a trusted machine (not random laptop), with the private signing key present:
```bash
./scripts/make-release.sh --source /path/to/fresh/client --version 2026.04.14-1 \
--previous 2026.04.13-3 --notes notes.md --dry-run
./scripts/make-release.sh --version 2026.04.14-1 --source /path/to/fresh/client
```
[`scripts/make-release.sh`](../scripts/make-release.sh) is the single entry
point for the v1 manual flow. It drives `make-manifest.py` + `sign-manifest.py`,
builds the content-addressed blob tree under `files/<hash[0:2]>/<hash>` with
hardlink-based deduplication, archives the signed manifest into
`manifests/<version>.json`, and — unless `--dry-run` is passed — rsyncs the
blob tree first and the `manifest.json` + `manifest.json.sig` pair last so the
release becomes visible atomically. Flags: `--key` (default
`~/.config/metin/launcher-signing-key`, must be mode 600), `--out` (default
`/tmp/release-<version>`), `--force` to overwrite a non-empty out dir, `--yes`
to skip the interactive rsync confirmation, `--rsync-target <user@host:/path>`
to override the upload destination.
2. The script walks the client directory, computes sha256 for each file, writes a `manifest.json`, signs it, and produces a release directory containing the manifest, its signature, and the deduplicated blob tree.
2. The script walks the client directory, computes sha256 for each file, writes a `manifest.json`, signs it, and produces a release directory `release/2026.04.14-1/` containing the manifest, its signature, and only the new blobs (ones not already present on the server).
3. Human review: diff the new manifest against the previous one, sanity-check size and file count.
4. Re-run without `--dry-run` (same args) to rsync to the VPS. The script prints the target and waits for confirmation unless `--yes` is passed.
4. `rsync` the release directory to the VPS:
```bash
rsync -av release/2026.04.14-1/ mt2.jakubkadlec.dev@mt2.jakubkadlec.dev:/var/www/updates.jakubkadlec.dev/
```
5. Verify from a second machine: `curl` the manifest, check signature, check a random blob.
6. Tag the release in git.

View File

@@ -1,169 +0,0 @@
#!/usr/bin/env bash
# make-release.sh — assemble, sign, and (optionally) publish a client release.
#
# Drives scripts/make-manifest.py + scripts/sign-manifest.py, then builds the
# content-addressed blob tree the launcher pulls from. See docs/update-manager.md
# for the end-to-end design; this script is the v1 manual publishing flow.
#
# Usage:
# scripts/make-release.sh --source <client-dir> --version <version> \
# [--previous <version>] [--notes <file>] \
# [--key <path>] [--out <path>] [--force] \
# [--dry-run] [--yes] [--rsync-target <user@host:/path>]
set -euo pipefail
# -------- arg parsing --------
SOURCE=""
VERSION=""
PREVIOUS=""
NOTES_FILE=""
KEY="${HOME}/.config/metin/launcher-signing-key"
OUT=""
FORCE=0
DRY_RUN=0
YES=0
RSYNC_TARGET="mt2.jakubkadlec.dev@mt2.jakubkadlec.dev:/var/www/updates.jakubkadlec.dev/"
die() { echo "error: $*" >&2; exit 1; }
say() { echo "[make-release] $*"; }
while [[ $# -gt 0 ]]; do
case "$1" in
--source) SOURCE="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
--previous) PREVIOUS="$2"; shift 2 ;;
--notes) NOTES_FILE="$2"; shift 2 ;;
--key) KEY="$2"; shift 2 ;;
--out) OUT="$2"; shift 2 ;;
--force) FORCE=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--yes) YES=1; shift ;;
--rsync-target) RSYNC_TARGET="$2"; shift 2 ;;
-h|--help) sed -n '1,15p' "$0"; exit 0 ;;
*) die "unknown arg: $1" ;;
esac
done
[[ -n "$SOURCE" ]] || die "--source is required"
[[ -n "$VERSION" ]] || die "--version is required"
[[ -d "$SOURCE" ]] || die "source dir does not exist: $SOURCE"
[[ -f "$SOURCE/Metin2.exe" ]] || die "source does not look like a client dir (missing Metin2.exe): $SOURCE"
[[ -f "$KEY" ]] || die "signing key not found: $KEY"
KEY_MODE=$(stat -c '%a' "$KEY")
[[ "$KEY_MODE" == "600" ]] || die "signing key $KEY must be mode 600, got $KEY_MODE"
[[ -n "$NOTES_FILE" && ! -f "$NOTES_FILE" ]] && die "notes file not found: $NOTES_FILE"
: "${OUT:=/tmp/release-${VERSION}}"
SOURCE=$(cd "$SOURCE" && pwd)
OUT_ABS=$(mkdir -p "$OUT" && cd "$OUT" && pwd)
if [[ -n "$(ls -A "$OUT_ABS" 2>/dev/null)" && "$FORCE" -ne 1 ]]; then
die "output directory $OUT_ABS is non-empty (use --force to overwrite)"
fi
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
say "source: $SOURCE"
say "version: $VERSION"
say "out: $OUT_ABS"
say "key: $KEY"
# -------- [1/6] manifest --------
say "[1/6] building manifest"
mkdir -p "$OUT_ABS/manifests" "$OUT_ABS/files"
MANIFEST="$OUT_ABS/manifest.json"
mk_args=(--source "$SOURCE" --version "$VERSION" --out "$MANIFEST")
[[ -n "$PREVIOUS" ]] && mk_args+=(--previous "$PREVIOUS")
if [[ -n "$NOTES_FILE" ]]; then
notes_text=$(cat "$NOTES_FILE")
mk_args+=(--notes "$notes_text")
fi
python3 "$SCRIPT_DIR/make-manifest.py" "${mk_args[@]}"
# -------- [2/6] sign --------
say "[2/6] signing manifest"
python3 "$SCRIPT_DIR/sign-manifest.py" --manifest "$MANIFEST" --key "$KEY"
SIG="$MANIFEST.sig"
[[ -f "$SIG" ]] || die "signature not produced"
sig_len=$(stat -c '%s' "$SIG")
[[ "$sig_len" == "64" ]] || die "signature is $sig_len bytes, expected 64"
# -------- [3/6] archive historical manifest --------
say "[3/6] archiving historical manifest -> manifests/${VERSION}.json"
cp -f "$MANIFEST" "$OUT_ABS/manifests/${VERSION}.json"
cp -f "$SIG" "$OUT_ABS/manifests/${VERSION}.json.sig"
# -------- [4/6] blob tree --------
say "[4/6] building content-addressed blob tree"
# Extract (path, sha256) pairs for launcher + every file entry.
mapfile -t PAIRS < <(jq -r '
([.launcher] + .files)
| .[]
| "\(.sha256)\t\(.path)"
' "$MANIFEST")
total_entries=${#PAIRS[@]}
unique_count=0
dedup_count=0
bytes_written=0
declare -A SEEN
for pair in "${PAIRS[@]}"; do
hash="${pair%%$'\t'*}"
rel="${pair#*$'\t'}"
src="$SOURCE/$rel"
[[ -f "$src" ]] || die "file in manifest missing from source: $rel"
if [[ -n "${SEEN[$hash]:-}" ]]; then
dedup_count=$((dedup_count + 1))
continue
fi
SEEN[$hash]=1
unique_count=$((unique_count + 1))
prefix="${hash:0:2}"
dst_dir="$OUT_ABS/files/$prefix"
dst="$dst_dir/$hash"
mkdir -p "$dst_dir"
if [[ -f "$dst" ]]; then
continue
fi
# Try hardlink first, fall back to copy across filesystems.
if ! cp -l "$src" "$dst" 2>/dev/null; then
cp "$src" "$dst"
fi
sz=$(stat -c '%s' "$dst")
bytes_written=$((bytes_written + sz))
done
say " entries: $total_entries unique blobs: $unique_count deduped: $dedup_count"
say " bytes written: $bytes_written"
# -------- [5/6] layout summary --------
say "[5/6] final layout:"
(cd "$OUT_ABS" && find . -maxdepth 2 -mindepth 1 -printf ' %p\n' | sort | head -40)
# -------- [6/6] rsync --------
if [[ "$DRY_RUN" -eq 1 ]]; then
say "[6/6] --dry-run set, skipping rsync. target would be: $RSYNC_TARGET"
exit 0
fi
say "[6/6] rsync target: $RSYNC_TARGET"
if [[ "$YES" -ne 1 ]]; then
read -r -p "continue? [y/N] " ans
[[ "$ans" == "y" || "$ans" == "Y" ]] || die "aborted by user"
fi
# Stage 1: everything except manifest.json(.sig) — blobs and historical archive.
rsync -av --delay-updates --checksum --omit-dir-times --no-perms \
--exclude 'manifest.json' --exclude 'manifest.json.sig' \
"$OUT_ABS"/ "$RSYNC_TARGET"
# Stage 2: manifest + signature, so the new release becomes visible last.
rsync -av --checksum --omit-dir-times --no-perms \
"$MANIFEST" "$SIG" "$RSYNC_TARGET"
say "done."