diff -r a323e1954a6f -r 014f4edd0421 hedgewars/uDrawing.pas --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hedgewars/uDrawing.pas Sun Oct 16 13:14:16 2022 +0300 @@ -0,0 +1,525 @@ +(* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2015 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + *) + +{$INCLUDE "options.inc"} + +unit uDrawing; +(* + * This unit defines the Drawing mode, which allows drawing some graphics + * private to the members of a clan. + *) +interface + +procedure initModule; +procedure freeModule; + +function isDrawingModeActive(): boolean; +procedure onModeButtonPressed(); +procedure onModeButtonReleased(); +procedure onFocusStateChanged(); +procedure onCursorMoved(); +procedure onLeftMouseButtonPressed(); +procedure onLeftMouseButtonReleased(); +procedure onRightMouseButtonPressed(); +procedure onMiddleMouseButtonPressed(); +procedure handleIPCInput(cmd : shortstring); + +implementation +uses uTypes, uConsts, uVariables, uVisualGearsList, uUtils, uDebug, uIO, SDLh, Math; + +const + cColorsCount = 9; + cColors : array [0..cColorsCount - 1] of LongWord = ( + $ff020400, + $4980c100, + $1de6ba00, + $b541ef00, + $e55bb000, + $20bf0000, + $fe8b0e00, + $8f590200, + $ffff0100 + ); + // Reserve one color for the local user + cKnownUsersMax = cColorsCount - 1; + cPointRadius = 25; + cBeaconDuration = 125; + cEffectDuration = 500; + cMaxDrawingRadius = 150; + cEffectGearsCountMax = 4; + +type + TDrawingState = (drwDisabled, drwStart, drwPoint, drwArrow); + + TVisualEffect = record + vGears : array [0..cEffectGearsCountMax - 1] of PVisualGear; + gearsCount : integer; + end; + TDrawingContext = record + state : TDrawingState; + currVEffect : TVisualEffect; + prevAutoCameraOn : boolean; + startCursorX : LongInt; + startCursorY : LongInt; + knownUsers : array [0..cKnownUsersMax - 1] of shortstring; + knownUsersCount : integer; + lastReplacedUserIdx : integer; + end; + +var drawingCtx : TDrawingContext; + +procedure AddKnownUser(user : shortstring); +var i : integer; +begin + with drawingCtx do + begin + for i:= 0 to knownUsersCount - 1 do + begin + if knownUsers[i] = user then + exit; + end; + if knownUsersCount < cKnownUsersMax then + begin + knownUsers[knownUsersCount]:= user; + Inc(knownUsersCount); + end + else + begin + lastReplacedUserIdx:= (lastReplacedUserIdx + 1) mod cKnownUsersMax; + knownUsers[lastReplacedUserIdx]:= user; + end; + end; +end; + +function GetUserColor(user : shortstring) : LongWord; +var i : integer; +begin + if user = '' then // local user + exit(cColors[0]); + with drawingCtx do + begin + for i:= 0 to knownUsersCount - 1 do + begin + if knownUsers[i] = user then + begin + exit(cColors[i + 1]); + end; + end; + exit(cColors[0]); + end; +end; + +procedure recalcArrowParams(var arrow: TVisualEffect; X1, Y1, X2, Y2 : real); +var tmp, tmpSin, tmpCos : real; +begin + with arrow.vGears[0]^ do + begin + X:= X1; + Y:= Y1; + dX:= X2; + dY:= Y2; + end; + // Compute arrow pointer coordinates + if X2 = X1 then + if Y2 > Y1 then + tmp:= PI / 2 + else + tmp:= -PI / 2 + else + tmp:= arctan2(Y2 - Y1, X2 - X1); + tmpSin:= sin(tmp - PI / 4); + tmpCos:= cos(tmp - PI / 4); + with arrow.vGears[1]^ do + begin + X:= X2; + Y:= Y2; + dX:= X2 - 50 * tmpCos; + dY:= Y2 - 50 * tmpSin; + end; + tmpSin:= sin(tmp + PI / 4); + tmpCos:= cos(tmp + PI / 4); + with arrow.vGears[2]^ do + begin + X:= X2; + Y:= Y2; + dX:= X2 - 50 * tmpCos; + dY:= Y2 - 50 * tmpSin; + end; + // Compute circle center + with arrow.vGears[3]^ do + begin + X:= (X1 + X2) / 2; + Y:= (Y1 + Y2) / 2; + end; +end; + +procedure doStepPoint(Gear: PVisualGear; Steps: Longword); +var tmp: LongInt; +begin +if Gear^.FrameTicks <= Steps then + DeleteVisualGear(Gear) +else +begin + dec(Gear^.FrameTicks, Steps); + if Gear^.Tag = 0 then + begin + tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration); + if tmp > $FF then + tmp:= $FF; + if tmp >= 0 then + Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp); + end + else if Gear^.Tag = 1 then + begin + Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration); + end; +end; +end; + +procedure doStepArrow(Gear: PVisualGear; Steps: Longword); +var tmp: LongInt; +begin +if Gear^.Tag = 100 then + exit; +if Gear^.FrameTicks <= Steps then + DeleteVisualGear(Gear) +else +begin + dec(Gear^.FrameTicks, Steps); + if Gear^.Tag < 3 then + begin + tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration); + if tmp > $FF then + tmp:= $FF; + if tmp >= 0 then + Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp); + end + else if Gear^.Tag = 3 then + begin + Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration); + end; +end; +end; + +function isEffectEmpty(var vEffect : TVisualEffect) : boolean; +begin + isEffectEmpty:= vEffect.gearsCount = 0; +end; + +function AddVEffectCircle(user : shortstring; X, Y : LongInt) : TVisualEffect; +var vGear : PVisualGear; + vEffect : TVisualEffect; + color : LongWord; + i : integer; +begin + color:= GetUserColor(user); + for i:= 0 to 1 do + begin + vGear := AddVisualGear(X, Y, vgtCircle, cPointRadius, true, 1); + if vGear = nil then + begin + OutError('uDrawing: AddVisualGear returned nil', false); + vEffect.gearsCount:= 0; + exit(vEffect); + end; + vGear^.Tint:= color or $FF; + vGear^.Angle:= 0; + vGear^.Timer:= 10; + vGear^.Tag:= i; + vGear^.doStep:= @doStepPoint; + vEffect.vGears[i]:= vGear; + end; + vEffect.vGears[0]^.Tint:= color or $FF; + vEffect.vGears[0]^.FrameTicks:= cEffectDuration; + vEffect.vGears[1]^.Tint:= color or $3F; + vEffect.vGears[1]^.FrameTicks:= cBeaconDuration; + + vEffect.gearsCount:= 2; + AddVEffectCircle:= vEffect; +end; + +function AddVEffectArrow(user : shortstring; X1, Y1, X2, Y2 : LongInt) : TVisualEffect; +var vGear : PVisualGear; + vEffect : TVisualEffect; + color : LongWord; + i : integer; +begin + color:= GetUserColor(user); + for i:= 0 to 2 do + begin + vGear := AddVisualGear(0, 0, vgtLine, 10, true, 1); + if vGear = nil then + begin + OutError('uDrawing: AddVisualGear returned nil', false); + vEffect.gearsCount:= 0; + exit(vEffect); + end; + vGear^.Tint:= color or $FF; + vGear^.FrameTicks:= cEffectDuration; + vGear^.Tag:= 100; + vGear^.doStep:= @doStepArrow; + vEffect.vGears[i]:= vGear; + end; + + vGear := AddVisualGear(0, 0, vgtCircle, 2048, true, 1); + if vGear = nil then + begin + OutError('uDrawing: AddVisualGear returned nil', false); + vEffect.gearsCount:= 0; + exit(vEffect); + end; + vGear^.Tint:= color; + vGear^.Angle:= 0; + vGear^.FrameTicks:= cBeaconDuration; + vGear^.Timer:= 10; + vGear^.Tag:= 100; + vGear^.doStep:= @doStepArrow; + vEffect.vGears[3]:= vGear; + + vEffect.gearsCount:= 4; + recalcArrowParams(vEffect, X1, Y1, X2, Y2); + + AddVEffectArrow:= vEffect; +end; + +procedure VEffectArrowStart(var vEffect : TVisualEffect); +var i : integer; +begin + for i:= 0 to vEffect.gearsCount - 1 do + vEffect.vGears[i]^.Tag:= i; + vEffect.vGears[3]^.Tint:= (vEffect.vGears[3]^.Tint and $FFFFFF00) or $3F; +end; + +procedure DeleteVEffect(var vEffect : TVisualEffect); +var i : integer; +begin + for i:= 0 to vEffect.gearsCount - 1 do + DeleteVisualGear(vEffect.vGears[i]); + vEffect.gearsCount:= 0; +end; + +function isDrawingModeActive() : boolean; +begin + isDrawingModeActive:= drawingCtx.state <> drwDisabled; +end; + +procedure SendIPCArrow(X1, Y1, X2, Y2: LongInt); +var s: shortstring; +begin +s[0]:= #18; +s[1]:= 'O'; +s[2]:= 'a'; +SDLNet_Write32(X1, @s[3]); +SDLNet_Write32(Y1, @s[7]); +SDLNet_Write32(X2, @s[11]); +SDLNet_Write32(Y2, @s[15]); +SendIPC(s) +end; + +procedure SendIPCCircle(X1, Y1: LongInt); +var s: shortstring; +begin +s[0]:= #10; +s[1]:= 'O'; +s[2]:= 'c'; +SDLNet_Write32(X1, @s[3]); +SDLNet_Write32(Y1, @s[7]); +SendIPC(s) +end; + +procedure handleIPCInput(cmd: shortstring); +var i, drwCmdOffset : integer; + userNameLen : Byte; + user : shortstring; + X1, Y1, X2, Y2 : LongInt; + VEffect : TVisualEffect; +begin +case cmd[1] of + 'u' : begin + userNameLen:= Byte(cmd[2]); + for i:= 0 to userNameLen do + user[i]:= cmd[2 + i]; + drwCmdOffset:= 2 + userNameLen + 1; + if Length(cmd) < drwCmdOffset then + exit; + AddKnownUser(user); + case cmd[drwCmdOffset] of + 'a' : begin + if Length(cmd) < drwCmdOffset + 4 * 4 then + exit; + X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]); + Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]); + X2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 8]); + Y2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 12]); + VEffect:= AddVEffectArrow(user, X1, Y1, X2, Y2); + if not isEffectEmpty(VEffect) then + VEffectArrowStart(VEffect); + end; + 'c' : begin + if Length(cmd) < drwCmdOffset + 4 * 2 then + exit; + X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]); + Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]); + VEffect:= AddVEffectCircle(user, X1, Y1); + end; + end; + end; +end; +end; + +procedure onModeButtonPressed(); +begin + drawingCtx.state:= drwStart; + drawingCtx.prevAutoCameraOn:= autoCameraOn; + autoCameraOn:= false; +end; + +procedure onModeButtonReleased(); +begin + DeleteVEffect(drawingCtx.currVEffect); + if drawingCtx.state <> drwDisabled then + begin + drawingCtx.state:= drwDisabled; + autoCameraOn:= drawingCtx.prevAutoCameraOn; + end; +end; + +procedure onFocusStateChanged(); +begin + if not cHasFocus then + onModeButtonReleased(); +end; + +procedure onLeftMouseButtonPressed(); +begin +if not isDrawingModeActive() then + exit; +case drawingCtx.state of + drwStart: begin + drawingCtx.startCursorX:= CursorPoint.X; + drawingCtx.startCursorY:= CursorPoint.Y; + drawingCtx.state:= drwPoint; + end; +end; +end; + +procedure onLeftMouseButtonReleased(); +var tmpX, tmpY, tmpX2, tmpY2 : LongInt; + vEffect : TVisualEffect; +begin +if not isDrawingModeActive() then + exit; +case drawingCtx.state of + drwPoint: begin + tmpX:= drawingCtx.startCursorX - WorldDx; + tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy; + vEffect:= AddVEffectCircle('', tmpX, tmpY); + if not isEffectEmpty(vEffect) then + SendIPCCircle(tmpX, tmpY); + drawingCtx.state:= drwStart; + end; + drwArrow: begin + tmpX2:= CursorPoint.X - WorldDx; + tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy; + with drawingCtx do + begin + tmpX:= startCursorX - WorldDx; + tmpY:= cScreenHeight - startCursorY - WorldDy; + recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2); + VEffectArrowStart(currVEffect); + SendIPCArrow(tmpX, tmpY, tmpX2, tmpY2); + currVEffect.gearsCount:= 0; + end; + drawingCtx.state:= drwStart; + end; +end; +end; + +procedure onRightMouseButtonPressed(); +begin + if not isDrawingModeActive() then + exit; + DeleteVEffect(drawingCtx.currVEffect); + drawingCtx.state:= drwStart; +end; + +procedure onMiddleMouseButtonPressed(); +begin +end; + +procedure onCursorMoved(); +var tmpX, tmpY, tmpX2, tmpY2, dX, dY : LongInt; + h : real; +begin +if not isDrawingModeActive() then + exit; +autoCameraOn:= false; +dX:= CursorPoint.X - drawingCtx.startCursorX; +dY:= CursorPoint.Y - drawingCtx.startCursorY; +h:= sqrt(dX * dX + dY * dY); +if (drawingCtx.state <> drwStart) and (h > cMaxDrawingRadius) then +begin + CursorPoint.X:= drawingCtx.startCursorX + round(dX * cMaxDrawingRadius / h); + CursorPoint.Y:= drawingCtx.startCursorY + round(dY * cMaxDrawingRadius / h); +end; +case drawingCtx.state of + drwPoint : begin + if h > cPointRadius then + begin + tmpX:= drawingCtx.startCursorX - WorldDx; + tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy; + tmpX2:= CursorPoint.X - WorldDx; + tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy; + drawingCtx.currVEffect:= AddVEffectArrow('', tmpX, tmpY, tmpX2, tmpY2); + if not isEffectEmpty(drawingCtx.currVEffect) then + drawingCtx.state:= drwArrow + else + drawingCtx.state:= drwStart; + end; + end; + drwArrow : begin + tmpX:= drawingCtx.startCursorX - WorldDx; + tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy; + tmpX2:= CursorPoint.X - WorldDx; + tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy; + with drawingCtx do + begin + recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2); + end; + end; +end; +end; + +procedure initModule; +begin + with drawingCtx do + begin + state:= drwDisabled; + currVEffect.gearsCount:= 0; + startCursorX:= 0; + startCursorY:= 0; + knownUsersCount:= 0; + lastReplacedUserIdx:= 0; + end; +end; + +procedure freeModule; +begin +end; + +end.