--- /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 <unC0Rr@gmail.com>
+ *
+ * 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.