@@ -17,7 +17,7 @@ import {
1717 WEAPONS ,
1818} from "@artillery/shared" ;
1919import { Sound } from "../audio/Sound" ;
20- import { TankView , getTankPreviewTextures } from "../entities/Tank" ;
20+ import { TankView } from "../entities/Tank" ;
2121import { TerrainView } from "../entities/TerrainView" ;
2222import { ProjectileView } from "../entities/ProjectileView" ;
2323import { FireView } from "../entities/FireView" ;
@@ -60,15 +60,6 @@ export class BattleScene extends Phaser.Scene {
6060 private reticle ! : Phaser . GameObjects . Graphics ;
6161 private dragOverlay ! : Phaser . GameObjects . Graphics ;
6262 private lastArcGfx ! : Phaser . GameObjects . Graphics ;
63- private aimLabel ?: Phaser . GameObjects . Text ;
64- private cursorPreview ?: {
65- container : Phaser . GameObjects . Container ;
66- hull : Phaser . GameObjects . Image ;
67- barrel : Phaser . GameObjects . Image ;
68- barrelOffsetX : number ;
69- barrelOffsetY : number ;
70- loadoutKey : string ;
71- } ;
7263 private windEmitter ?: Phaser . GameObjects . Particles . ParticleEmitter ;
7364 private cursorOnCanvas = false ;
7465 private cursorScreenX = 0 ;
@@ -359,7 +350,7 @@ export class BattleScene extends Phaser.Scene {
359350 0 ,
360351 Math . min ( 1 , ( power - TANK . MIN_POWER ) / ( TANK . MAX_POWER - TANK . MIN_POWER ) ) ,
361352 ) ;
362- const arrowLen = 40 + powerT * 90 ;
353+ const arrowLen = 32 + Math . pow ( Math . max ( 0 , powerT ) , 1.4 ) * 320 ;
363354 const tipX = baseX + dirX * arrowLen ;
364355 const tipY = baseY + dirY * arrowLen ;
365356 this . lastArcGfx . lineStyle ( 2 , color , 0.6 ) ;
@@ -938,10 +929,6 @@ export class BattleScene extends Phaser.Scene {
938929 this . aimLine . clear ( ) ;
939930 this . reticle . clear ( ) ;
940931 this . dragOverlay . clear ( ) ;
941- if ( ! this . dragging ) {
942- if ( this . aimLabel ) this . aimLabel . setVisible ( false ) ;
943- if ( this . cursorPreview ) this . cursorPreview . container . setVisible ( false ) ;
944- }
945932
946933 if ( ! this . isMyTurn ( ) ) return ;
947934 const self = this . room . state . players . get ( this . room . sessionId ) ;
@@ -975,9 +962,16 @@ export class BattleScene extends Phaser.Scene {
975962 const perpX = - dirY ;
976963 const perpY = dirX ;
977964
965+ // Range scales with v² in the physics, so use a slight power curve
966+ // so 100% reads as "across-the-map" rather than just "a bit longer".
967+ const ARROW_BASE = 32 ;
968+ const ARROW_RANGE = 320 ;
969+ const ARROW_MAX = ARROW_BASE + ARROW_RANGE ;
970+ const arrowScale = ( t : number ) => Math . pow ( Math . max ( 0 , t ) , 1.4 ) ;
971+ const arrowLen = ARROW_BASE + arrowScale ( powerT ) * ARROW_RANGE ;
972+
978973 if ( ! dense ) {
979974 // Idle: thin tracer of the current aim. No extra chrome.
980- const arrowLen = 40 + powerT * 90 ;
981975 const tipX = baseX + dirX * arrowLen ;
982976 const tipY = baseY + dirY * arrowLen ;
983977 this . aimLine . lineStyle ( 2 , 0xd49228 , 0.6 ) ;
@@ -997,113 +991,55 @@ export class BattleScene extends Phaser.Scene {
997991 // Active drag: same scale as idle arrow, just thicker line and
998992 // power-coloured tier tint. Keeps geometry consistent so it never
999993 // jumps in size when drag begins.
1000- const arrowLen = 40 + powerT * 90 ;
1001994 const tipX = baseX + dirX * arrowLen ;
1002995 const tipY = baseY + dirY * arrowLen ;
1003996 const tier =
1004997 powerT > 0.9 ? 0xff5a3c :
1005998 powerT > 0.65 ? 0xff8a2a :
1006999 powerT > 0.35 ? 0xffbe52 : 0xf0e090 ;
1007- this . aimLine . lineStyle ( 3 , tier , 0.95 ) ;
1000+ // Steps up at the same thresholds as the tier colour so each
1001+ // crossing is felt: thicker shaft + bigger arrowhead.
1002+ const shaftWidth =
1003+ powerT > 0.9 ? 6 :
1004+ powerT > 0.65 ? 5 :
1005+ powerT > 0.35 ? 4 : 3 ;
1006+ const headLen = 6 + shaftWidth ;
1007+ const headHalf = 3 + shaftWidth * 0.6 ;
1008+
1009+ // Faint full-range track behind the live arrow plus perpendicular
1010+ // tick marks at 25/50/75/100% so the player can read power without
1011+ // chasing the moving tip.
1012+ const trackEndX = baseX + dirX * ARROW_MAX ;
1013+ const trackEndY = baseY + dirY * ARROW_MAX ;
1014+ this . aimLine . lineStyle ( 1 , 0xffffff , 0.18 ) ;
1015+ this . aimLine . beginPath ( ) ;
1016+ this . aimLine . moveTo ( baseX , baseY ) ;
1017+ this . aimLine . lineTo ( trackEndX , trackEndY ) ;
1018+ this . aimLine . strokePath ( ) ;
1019+ for ( const frac of [ 0.25 , 0.5 , 0.75 , 1 ] ) {
1020+ const tickLen = ARROW_BASE + arrowScale ( frac ) * ARROW_RANGE ;
1021+ const tx = baseX + dirX * tickLen ;
1022+ const ty = baseY + dirY * tickLen ;
1023+ const half = frac === 1 ? 7 : 5 ;
1024+ const reached = powerT >= frac - 0.001 ;
1025+ this . aimLine . lineStyle ( reached ? 2 : 1 , reached ? tier : 0xffffff , reached ? 0.85 : 0.32 ) ;
1026+ this . aimLine . beginPath ( ) ;
1027+ this . aimLine . moveTo ( tx + perpX * half , ty + perpY * half ) ;
1028+ this . aimLine . lineTo ( tx - perpX * half , ty - perpY * half ) ;
1029+ this . aimLine . strokePath ( ) ;
1030+ }
1031+
1032+ this . aimLine . lineStyle ( shaftWidth , tier , 0.95 ) ;
10081033 this . aimLine . beginPath ( ) ;
10091034 this . aimLine . moveTo ( baseX , baseY ) ;
10101035 this . aimLine . lineTo ( tipX , tipY ) ;
10111036 this . aimLine . strokePath ( ) ;
10121037 this . aimLine . fillStyle ( tier , 0.95 ) ;
10131038 this . aimLine . fillTriangle (
1014- tipX + dirX * 8 , tipY + dirY * 8 ,
1015- tipX - dirX * 3 + perpX * 5 , tipY - dirY * 3 + perpY * 5 ,
1016- tipX - dirX * 3 - perpX * 5 , tipY - dirY * 3 - perpY * 5 ,
1039+ tipX + dirX * headLen , tipY + dirY * headLen ,
1040+ tipX - dirX * 3 + perpX * headHalf , tipY - dirY * 3 + perpY * headHalf ,
1041+ tipX - dirX * 3 - perpX * headHalf , tipY - dirY * 3 - perpY * headHalf ,
10171042 ) ;
1018-
1019- // Cursor tank preview — a mini copy of the local player's tank
1020- // anchored at the pointer, with the barrel rotated to match the
1021- // current aim. Reuses the in-game hull/barrel textures so colours,
1022- // pattern, decal, and turret shape all match instantly.
1023- const ptr = this . input . activePointer ;
1024- this . renderCursorPreview ( self , ptr . x , ptr . y , angleDeg , facing , powerT , tier ) ;
1025- }
1026-
1027- private renderCursorPreview (
1028- self : Player ,
1029- cx : number ,
1030- cy : number ,
1031- angleDeg : number ,
1032- facing : - 1 | 1 ,
1033- powerT : number ,
1034- tier : number ,
1035- ) : void {
1036- const SCALE = 0.7 ;
1037- const PREVIEW_OFFSET_Y = - 12 ;
1038-
1039- const loadoutKey =
1040- `${ self . bodyStyle } |${ self . turretStyle } |${ self . barrelStyle } |${ self . color } |${ self . accentColor } |${ self . pattern } |${ self . patternColor } |${ self . decal } ` ;
1041-
1042- let p = this . cursorPreview ;
1043- if ( ! p || p . loadoutKey !== loadoutKey ) {
1044- // Tear down any stale preview (loadout changed mid-match).
1045- if ( p ) p . container . destroy ( true ) ;
1046- const meta = getTankPreviewTextures ( this , self ) ;
1047- const container = this . add . container ( 0 , 0 )
1048- . setDepth ( 16 )
1049- . setScrollFactor ( 0 ) ;
1050- const hull = this . add . image ( 0 , 0 , meta . hull . textureKey )
1051- . setOrigin (
1052- meta . hull . hullCenterX / meta . hull . widthLogical ,
1053- meta . hull . hullCenterY / meta . hull . heightLogical ,
1054- )
1055- . setDisplaySize ( meta . hull . widthLogical , meta . hull . heightLogical ) ;
1056- const barrelOffsetX = meta . hull . barrelPivotX - meta . hull . hullCenterX ;
1057- const barrelOffsetY = meta . hull . barrelPivotY - meta . hull . hullCenterY ;
1058- const barrel = this . add . image ( barrelOffsetX , barrelOffsetY , meta . barrel . textureKey )
1059- . setOrigin (
1060- meta . barrel . pivotX / meta . barrel . widthLogical ,
1061- meta . barrel . pivotY / meta . barrel . heightLogical ,
1062- )
1063- . setDisplaySize ( meta . barrel . widthLogical , meta . barrel . heightLogical ) ;
1064- container . add ( [ hull , barrel ] ) ;
1065- p = { container, hull, barrel, barrelOffsetX, barrelOffsetY, loadoutKey } ;
1066- this . cursorPreview = p ;
1067- }
1068-
1069- p . container . setVisible ( true ) ;
1070- p . container . setPosition ( cx , cy + PREVIEW_OFFSET_Y ) ;
1071- p . container . setScale ( SCALE * facing , SCALE ) ;
1072- const angleRad = ( angleDeg * Math . PI ) / 180 ;
1073- p . barrel . setRotation ( - angleRad ) ;
1074-
1075- // Power ring drawn around the preview — faint full circle plus a
1076- // bright arc filling clockwise as power climbs.
1077- const ringR = 32 ;
1078- this . dragOverlay . lineStyle ( 2 , 0xffffff , 0.18 ) ;
1079- this . dragOverlay . strokeCircle ( cx , cy + PREVIEW_OFFSET_Y , ringR ) ;
1080- this . dragOverlay . lineStyle ( 3 , tier , 0.9 ) ;
1081- this . dragOverlay . beginPath ( ) ;
1082- this . dragOverlay . arc (
1083- cx , cy + PREVIEW_OFFSET_Y , ringR ,
1084- - Math . PI / 2 ,
1085- - Math . PI / 2 + powerT * Math . PI * 2 ,
1086- ) ;
1087- this . dragOverlay . strokePath ( ) ;
1088-
1089- // Power % label below the ring — single readout, no overlap with
1090- // arrow since the arrow now lives at the tank.
1091- const txt = this . aimLabel ?? this . add
1092- . text ( 0 , 0 , "" , {
1093- fontFamily : "JetBrains Mono, monospace" ,
1094- fontSize : "12px" ,
1095- color : "#ffffff" ,
1096- backgroundColor : "rgba(11,16,32,0.9)" ,
1097- padding : { left : 6 , right : 6 , top : 2 , bottom : 2 } ,
1098- } )
1099- . setDepth ( 17 )
1100- . setScrollFactor ( 0 )
1101- . setOrigin ( 0.5 , 0 ) ;
1102- this . aimLabel = txt ;
1103- txt . setText ( `${ Math . abs ( angleDeg ) . toFixed ( 0 ) } ° · ${ Math . round ( powerT * 100 ) } %` ) ;
1104- txt . setColor ( `#${ tier . toString ( 16 ) . padStart ( 6 , "0" ) } ` ) ;
1105- txt . setPosition ( cx , cy + PREVIEW_OFFSET_Y + ringR + 6 ) ;
1106- txt . setVisible ( true ) ;
11071043 }
11081044
11091045 private updateCamera ( dt : number ) : void {
0 commit comments