|
1 (* |
|
2 * Hedgewars, a free turn based strategy game |
|
3 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com> |
|
4 * |
|
5 * This program is free software; you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation; version 2 of the License |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 * GNU General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU General Public License |
|
15 * along with this program; if not, write to the Free Software |
|
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
17 *) |
|
18 |
|
19 {$INCLUDE "options.inc"} |
|
20 |
|
21 unit uDrawing; |
|
22 (* |
|
23 * This unit defines the Drawing mode, which allows drawing some graphics |
|
24 * private to the members of a clan. |
|
25 *) |
|
26 interface |
|
27 |
|
28 procedure initModule; |
|
29 procedure freeModule; |
|
30 |
|
31 function isDrawingModeActive(): boolean; |
|
32 procedure onModeButtonPressed(); |
|
33 procedure onModeButtonReleased(); |
|
34 procedure onFocusStateChanged(); |
|
35 procedure onCursorMoved(); |
|
36 procedure onLeftMouseButtonPressed(); |
|
37 procedure onLeftMouseButtonReleased(); |
|
38 procedure onRightMouseButtonPressed(); |
|
39 procedure onMiddleMouseButtonPressed(); |
|
40 procedure handleIPCInput(cmd : shortstring); |
|
41 |
|
42 implementation |
|
43 uses uTypes, uConsts, uVariables, uVisualGearsList, uUtils, uDebug, uIO, SDLh, Math; |
|
44 |
|
45 const |
|
46 cColorsCount = 9; |
|
47 cColors : array [0..cColorsCount - 1] of LongWord = ( |
|
48 $ff020400, |
|
49 $4980c100, |
|
50 $1de6ba00, |
|
51 $b541ef00, |
|
52 $e55bb000, |
|
53 $20bf0000, |
|
54 $fe8b0e00, |
|
55 $8f590200, |
|
56 $ffff0100 |
|
57 ); |
|
58 // Reserve one color for the local user |
|
59 cKnownUsersMax = cColorsCount - 1; |
|
60 cPointRadius = 25; |
|
61 cBeaconDuration = 125; |
|
62 cEffectDuration = 500; |
|
63 cMaxDrawingRadius = 150; |
|
64 cEffectGearsCountMax = 4; |
|
65 |
|
66 type |
|
67 TDrawingState = (drwDisabled, drwStart, drwPoint, drwArrow); |
|
68 |
|
69 TVisualEffect = record |
|
70 vGears : array [0..cEffectGearsCountMax - 1] of PVisualGear; |
|
71 gearsCount : integer; |
|
72 end; |
|
73 TDrawingContext = record |
|
74 state : TDrawingState; |
|
75 currVEffect : TVisualEffect; |
|
76 prevAutoCameraOn : boolean; |
|
77 startCursorX : LongInt; |
|
78 startCursorY : LongInt; |
|
79 knownUsers : array [0..cKnownUsersMax - 1] of shortstring; |
|
80 knownUsersCount : integer; |
|
81 lastReplacedUserIdx : integer; |
|
82 end; |
|
83 |
|
84 var drawingCtx : TDrawingContext; |
|
85 |
|
86 procedure AddKnownUser(user : shortstring); |
|
87 var i : integer; |
|
88 begin |
|
89 with drawingCtx do |
|
90 begin |
|
91 for i:= 0 to knownUsersCount - 1 do |
|
92 begin |
|
93 if knownUsers[i] = user then |
|
94 exit; |
|
95 end; |
|
96 if knownUsersCount < cKnownUsersMax then |
|
97 begin |
|
98 knownUsers[knownUsersCount]:= user; |
|
99 Inc(knownUsersCount); |
|
100 end |
|
101 else |
|
102 begin |
|
103 lastReplacedUserIdx:= (lastReplacedUserIdx + 1) mod cKnownUsersMax; |
|
104 knownUsers[lastReplacedUserIdx]:= user; |
|
105 end; |
|
106 end; |
|
107 end; |
|
108 |
|
109 function GetUserColor(user : shortstring) : LongWord; |
|
110 var i : integer; |
|
111 begin |
|
112 if user = '' then // local user |
|
113 exit(cColors[0]); |
|
114 with drawingCtx do |
|
115 begin |
|
116 for i:= 0 to knownUsersCount - 1 do |
|
117 begin |
|
118 if knownUsers[i] = user then |
|
119 begin |
|
120 exit(cColors[i + 1]); |
|
121 end; |
|
122 end; |
|
123 exit(cColors[0]); |
|
124 end; |
|
125 end; |
|
126 |
|
127 procedure recalcArrowParams(var arrow: TVisualEffect; X1, Y1, X2, Y2 : real); |
|
128 var tmp, tmpSin, tmpCos : real; |
|
129 begin |
|
130 with arrow.vGears[0]^ do |
|
131 begin |
|
132 X:= X1; |
|
133 Y:= Y1; |
|
134 dX:= X2; |
|
135 dY:= Y2; |
|
136 end; |
|
137 // Compute arrow pointer coordinates |
|
138 if X2 = X1 then |
|
139 if Y2 > Y1 then |
|
140 tmp:= PI / 2 |
|
141 else |
|
142 tmp:= -PI / 2 |
|
143 else |
|
144 tmp:= arctan2(Y2 - Y1, X2 - X1); |
|
145 tmpSin:= sin(tmp - PI / 4); |
|
146 tmpCos:= cos(tmp - PI / 4); |
|
147 with arrow.vGears[1]^ do |
|
148 begin |
|
149 X:= X2; |
|
150 Y:= Y2; |
|
151 dX:= X2 - 50 * tmpCos; |
|
152 dY:= Y2 - 50 * tmpSin; |
|
153 end; |
|
154 tmpSin:= sin(tmp + PI / 4); |
|
155 tmpCos:= cos(tmp + PI / 4); |
|
156 with arrow.vGears[2]^ do |
|
157 begin |
|
158 X:= X2; |
|
159 Y:= Y2; |
|
160 dX:= X2 - 50 * tmpCos; |
|
161 dY:= Y2 - 50 * tmpSin; |
|
162 end; |
|
163 // Compute circle center |
|
164 with arrow.vGears[3]^ do |
|
165 begin |
|
166 X:= (X1 + X2) / 2; |
|
167 Y:= (Y1 + Y2) / 2; |
|
168 end; |
|
169 end; |
|
170 |
|
171 procedure doStepPoint(Gear: PVisualGear; Steps: Longword); |
|
172 var tmp: LongInt; |
|
173 begin |
|
174 if Gear^.FrameTicks <= Steps then |
|
175 DeleteVisualGear(Gear) |
|
176 else |
|
177 begin |
|
178 dec(Gear^.FrameTicks, Steps); |
|
179 if Gear^.Tag = 0 then |
|
180 begin |
|
181 tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration); |
|
182 if tmp > $FF then |
|
183 tmp:= $FF; |
|
184 if tmp >= 0 then |
|
185 Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp); |
|
186 end |
|
187 else if Gear^.Tag = 1 then |
|
188 begin |
|
189 Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration); |
|
190 end; |
|
191 end; |
|
192 end; |
|
193 |
|
194 procedure doStepArrow(Gear: PVisualGear; Steps: Longword); |
|
195 var tmp: LongInt; |
|
196 begin |
|
197 if Gear^.Tag = 100 then |
|
198 exit; |
|
199 if Gear^.FrameTicks <= Steps then |
|
200 DeleteVisualGear(Gear) |
|
201 else |
|
202 begin |
|
203 dec(Gear^.FrameTicks, Steps); |
|
204 if Gear^.Tag < 3 then |
|
205 begin |
|
206 tmp:= round(Gear^.FrameTicks * $FF / cEffectDuration); |
|
207 if tmp > $FF then |
|
208 tmp:= $FF; |
|
209 if tmp >= 0 then |
|
210 Gear^.Tint:= (Gear^.Tint and $FFFFFF00) or Longword(tmp); |
|
211 end |
|
212 else if Gear^.Tag = 3 then |
|
213 begin |
|
214 Gear^.State:= round(Gear^.FrameTicks * 2048 / cBeaconDuration); |
|
215 end; |
|
216 end; |
|
217 end; |
|
218 |
|
219 function isEffectEmpty(var vEffect : TVisualEffect) : boolean; |
|
220 begin |
|
221 isEffectEmpty:= vEffect.gearsCount = 0; |
|
222 end; |
|
223 |
|
224 function AddVEffectCircle(user : shortstring; X, Y : LongInt) : TVisualEffect; |
|
225 var vGear : PVisualGear; |
|
226 vEffect : TVisualEffect; |
|
227 color : LongWord; |
|
228 i : integer; |
|
229 begin |
|
230 color:= GetUserColor(user); |
|
231 for i:= 0 to 1 do |
|
232 begin |
|
233 vGear := AddVisualGear(X, Y, vgtCircle, cPointRadius, true, 1); |
|
234 if vGear = nil then |
|
235 begin |
|
236 OutError('uDrawing: AddVisualGear returned nil', false); |
|
237 vEffect.gearsCount:= 0; |
|
238 exit(vEffect); |
|
239 end; |
|
240 vGear^.Tint:= color or $FF; |
|
241 vGear^.Angle:= 0; |
|
242 vGear^.Timer:= 10; |
|
243 vGear^.Tag:= i; |
|
244 vGear^.doStep:= @doStepPoint; |
|
245 vEffect.vGears[i]:= vGear; |
|
246 end; |
|
247 vEffect.vGears[0]^.Tint:= color or $FF; |
|
248 vEffect.vGears[0]^.FrameTicks:= cEffectDuration; |
|
249 vEffect.vGears[1]^.Tint:= color or $3F; |
|
250 vEffect.vGears[1]^.FrameTicks:= cBeaconDuration; |
|
251 |
|
252 vEffect.gearsCount:= 2; |
|
253 AddVEffectCircle:= vEffect; |
|
254 end; |
|
255 |
|
256 function AddVEffectArrow(user : shortstring; X1, Y1, X2, Y2 : LongInt) : TVisualEffect; |
|
257 var vGear : PVisualGear; |
|
258 vEffect : TVisualEffect; |
|
259 color : LongWord; |
|
260 i : integer; |
|
261 begin |
|
262 color:= GetUserColor(user); |
|
263 for i:= 0 to 2 do |
|
264 begin |
|
265 vGear := AddVisualGear(0, 0, vgtLine, 10, true, 1); |
|
266 if vGear = nil then |
|
267 begin |
|
268 OutError('uDrawing: AddVisualGear returned nil', false); |
|
269 vEffect.gearsCount:= 0; |
|
270 exit(vEffect); |
|
271 end; |
|
272 vGear^.Tint:= color or $FF; |
|
273 vGear^.FrameTicks:= cEffectDuration; |
|
274 vGear^.Tag:= 100; |
|
275 vGear^.doStep:= @doStepArrow; |
|
276 vEffect.vGears[i]:= vGear; |
|
277 end; |
|
278 |
|
279 vGear := AddVisualGear(0, 0, vgtCircle, 2048, true, 1); |
|
280 if vGear = nil then |
|
281 begin |
|
282 OutError('uDrawing: AddVisualGear returned nil', false); |
|
283 vEffect.gearsCount:= 0; |
|
284 exit(vEffect); |
|
285 end; |
|
286 vGear^.Tint:= color; |
|
287 vGear^.Angle:= 0; |
|
288 vGear^.FrameTicks:= cBeaconDuration; |
|
289 vGear^.Timer:= 10; |
|
290 vGear^.Tag:= 100; |
|
291 vGear^.doStep:= @doStepArrow; |
|
292 vEffect.vGears[3]:= vGear; |
|
293 |
|
294 vEffect.gearsCount:= 4; |
|
295 recalcArrowParams(vEffect, X1, Y1, X2, Y2); |
|
296 |
|
297 AddVEffectArrow:= vEffect; |
|
298 end; |
|
299 |
|
300 procedure VEffectArrowStart(var vEffect : TVisualEffect); |
|
301 var i : integer; |
|
302 begin |
|
303 for i:= 0 to vEffect.gearsCount - 1 do |
|
304 vEffect.vGears[i]^.Tag:= i; |
|
305 vEffect.vGears[3]^.Tint:= (vEffect.vGears[3]^.Tint and $FFFFFF00) or $3F; |
|
306 end; |
|
307 |
|
308 procedure DeleteVEffect(var vEffect : TVisualEffect); |
|
309 var i : integer; |
|
310 begin |
|
311 for i:= 0 to vEffect.gearsCount - 1 do |
|
312 DeleteVisualGear(vEffect.vGears[i]); |
|
313 vEffect.gearsCount:= 0; |
|
314 end; |
|
315 |
|
316 function isDrawingModeActive() : boolean; |
|
317 begin |
|
318 isDrawingModeActive:= drawingCtx.state <> drwDisabled; |
|
319 end; |
|
320 |
|
321 procedure SendIPCArrow(X1, Y1, X2, Y2: LongInt); |
|
322 var s: shortstring; |
|
323 begin |
|
324 s[0]:= #18; |
|
325 s[1]:= 'O'; |
|
326 s[2]:= 'a'; |
|
327 SDLNet_Write32(X1, @s[3]); |
|
328 SDLNet_Write32(Y1, @s[7]); |
|
329 SDLNet_Write32(X2, @s[11]); |
|
330 SDLNet_Write32(Y2, @s[15]); |
|
331 SendIPC(s) |
|
332 end; |
|
333 |
|
334 procedure SendIPCCircle(X1, Y1: LongInt); |
|
335 var s: shortstring; |
|
336 begin |
|
337 s[0]:= #10; |
|
338 s[1]:= 'O'; |
|
339 s[2]:= 'c'; |
|
340 SDLNet_Write32(X1, @s[3]); |
|
341 SDLNet_Write32(Y1, @s[7]); |
|
342 SendIPC(s) |
|
343 end; |
|
344 |
|
345 procedure handleIPCInput(cmd: shortstring); |
|
346 var i, drwCmdOffset : integer; |
|
347 userNameLen : Byte; |
|
348 user : shortstring; |
|
349 X1, Y1, X2, Y2 : LongInt; |
|
350 VEffect : TVisualEffect; |
|
351 begin |
|
352 case cmd[1] of |
|
353 'u' : begin |
|
354 userNameLen:= Byte(cmd[2]); |
|
355 for i:= 0 to userNameLen do |
|
356 user[i]:= cmd[2 + i]; |
|
357 drwCmdOffset:= 2 + userNameLen + 1; |
|
358 if Length(cmd) < drwCmdOffset then |
|
359 exit; |
|
360 AddKnownUser(user); |
|
361 case cmd[drwCmdOffset] of |
|
362 'a' : begin |
|
363 if Length(cmd) < drwCmdOffset + 4 * 4 then |
|
364 exit; |
|
365 X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]); |
|
366 Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]); |
|
367 X2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 8]); |
|
368 Y2:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 12]); |
|
369 VEffect:= AddVEffectArrow(user, X1, Y1, X2, Y2); |
|
370 if not isEffectEmpty(VEffect) then |
|
371 VEffectArrowStart(VEffect); |
|
372 end; |
|
373 'c' : begin |
|
374 if Length(cmd) < drwCmdOffset + 4 * 2 then |
|
375 exit; |
|
376 X1:= SDLNet_Read32(@cmd[drwCmdOffset + 1]); |
|
377 Y1:= SDLNet_Read32(@cmd[drwCmdOffset + 1 + 4]); |
|
378 VEffect:= AddVEffectCircle(user, X1, Y1); |
|
379 end; |
|
380 end; |
|
381 end; |
|
382 end; |
|
383 end; |
|
384 |
|
385 procedure onModeButtonPressed(); |
|
386 begin |
|
387 drawingCtx.state:= drwStart; |
|
388 drawingCtx.prevAutoCameraOn:= autoCameraOn; |
|
389 autoCameraOn:= false; |
|
390 end; |
|
391 |
|
392 procedure onModeButtonReleased(); |
|
393 begin |
|
394 DeleteVEffect(drawingCtx.currVEffect); |
|
395 if drawingCtx.state <> drwDisabled then |
|
396 begin |
|
397 drawingCtx.state:= drwDisabled; |
|
398 autoCameraOn:= drawingCtx.prevAutoCameraOn; |
|
399 end; |
|
400 end; |
|
401 |
|
402 procedure onFocusStateChanged(); |
|
403 begin |
|
404 if not cHasFocus then |
|
405 onModeButtonReleased(); |
|
406 end; |
|
407 |
|
408 procedure onLeftMouseButtonPressed(); |
|
409 begin |
|
410 if not isDrawingModeActive() then |
|
411 exit; |
|
412 case drawingCtx.state of |
|
413 drwStart: begin |
|
414 drawingCtx.startCursorX:= CursorPoint.X; |
|
415 drawingCtx.startCursorY:= CursorPoint.Y; |
|
416 drawingCtx.state:= drwPoint; |
|
417 end; |
|
418 end; |
|
419 end; |
|
420 |
|
421 procedure onLeftMouseButtonReleased(); |
|
422 var tmpX, tmpY, tmpX2, tmpY2 : LongInt; |
|
423 vEffect : TVisualEffect; |
|
424 begin |
|
425 if not isDrawingModeActive() then |
|
426 exit; |
|
427 case drawingCtx.state of |
|
428 drwPoint: begin |
|
429 tmpX:= drawingCtx.startCursorX - WorldDx; |
|
430 tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy; |
|
431 vEffect:= AddVEffectCircle('', tmpX, tmpY); |
|
432 if not isEffectEmpty(vEffect) then |
|
433 SendIPCCircle(tmpX, tmpY); |
|
434 drawingCtx.state:= drwStart; |
|
435 end; |
|
436 drwArrow: begin |
|
437 tmpX2:= CursorPoint.X - WorldDx; |
|
438 tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy; |
|
439 with drawingCtx do |
|
440 begin |
|
441 tmpX:= startCursorX - WorldDx; |
|
442 tmpY:= cScreenHeight - startCursorY - WorldDy; |
|
443 recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2); |
|
444 VEffectArrowStart(currVEffect); |
|
445 SendIPCArrow(tmpX, tmpY, tmpX2, tmpY2); |
|
446 currVEffect.gearsCount:= 0; |
|
447 end; |
|
448 drawingCtx.state:= drwStart; |
|
449 end; |
|
450 end; |
|
451 end; |
|
452 |
|
453 procedure onRightMouseButtonPressed(); |
|
454 begin |
|
455 if not isDrawingModeActive() then |
|
456 exit; |
|
457 DeleteVEffect(drawingCtx.currVEffect); |
|
458 drawingCtx.state:= drwStart; |
|
459 end; |
|
460 |
|
461 procedure onMiddleMouseButtonPressed(); |
|
462 begin |
|
463 end; |
|
464 |
|
465 procedure onCursorMoved(); |
|
466 var tmpX, tmpY, tmpX2, tmpY2, dX, dY : LongInt; |
|
467 h : real; |
|
468 begin |
|
469 if not isDrawingModeActive() then |
|
470 exit; |
|
471 autoCameraOn:= false; |
|
472 dX:= CursorPoint.X - drawingCtx.startCursorX; |
|
473 dY:= CursorPoint.Y - drawingCtx.startCursorY; |
|
474 h:= sqrt(dX * dX + dY * dY); |
|
475 if (drawingCtx.state <> drwStart) and (h > cMaxDrawingRadius) then |
|
476 begin |
|
477 CursorPoint.X:= drawingCtx.startCursorX + round(dX * cMaxDrawingRadius / h); |
|
478 CursorPoint.Y:= drawingCtx.startCursorY + round(dY * cMaxDrawingRadius / h); |
|
479 end; |
|
480 case drawingCtx.state of |
|
481 drwPoint : begin |
|
482 if h > cPointRadius then |
|
483 begin |
|
484 tmpX:= drawingCtx.startCursorX - WorldDx; |
|
485 tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy; |
|
486 tmpX2:= CursorPoint.X - WorldDx; |
|
487 tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy; |
|
488 drawingCtx.currVEffect:= AddVEffectArrow('', tmpX, tmpY, tmpX2, tmpY2); |
|
489 if not isEffectEmpty(drawingCtx.currVEffect) then |
|
490 drawingCtx.state:= drwArrow |
|
491 else |
|
492 drawingCtx.state:= drwStart; |
|
493 end; |
|
494 end; |
|
495 drwArrow : begin |
|
496 tmpX:= drawingCtx.startCursorX - WorldDx; |
|
497 tmpY:= cScreenHeight - drawingCtx.startCursorY - WorldDy; |
|
498 tmpX2:= CursorPoint.X - WorldDx; |
|
499 tmpY2:= cScreenHeight - CursorPoint.Y - WorldDy; |
|
500 with drawingCtx do |
|
501 begin |
|
502 recalcArrowParams(currVEffect, tmpX, tmpY, tmpX2, tmpY2); |
|
503 end; |
|
504 end; |
|
505 end; |
|
506 end; |
|
507 |
|
508 procedure initModule; |
|
509 begin |
|
510 with drawingCtx do |
|
511 begin |
|
512 state:= drwDisabled; |
|
513 currVEffect.gearsCount:= 0; |
|
514 startCursorX:= 0; |
|
515 startCursorY:= 0; |
|
516 knownUsersCount:= 0; |
|
517 lastReplacedUserIdx:= 0; |
|
518 end; |
|
519 end; |
|
520 |
|
521 procedure freeModule; |
|
522 begin |
|
523 end; |
|
524 |
|
525 end. |