1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: LineSpecialLevelInfo.vc 4140 2010-03-04 23:04:43Z firebrand_kh $ 11//** 12//** Copyright (C) 1999-2006 Jānis Legzdiņš 13//** 14//** This program is free software; you can redistribute it and/or 15//** modify it under the terms of the GNU General Public License 16//** as published by the Free Software Foundation; either version 2 17//** of the License, or (at your option) any later version. 18//** 19//** This program is distributed in the hope that it will be useful, 20//** but WITHOUT ANY WARRANTY; without even the implied warranty of 21//** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22//** GNU General Public License for more details. 23//** 24//************************************************************************** 25 26class LineSpecialLevelInfo : LevelInfo; 27 28const int 29 STROBEBRIGHT = 5, 30 FASTDARK = 15, 31 SLOWDARK = 35; 32 33// 34// Map things flags 35// 36const int 37 MTF_AMBUSH = 0x0008, // Deaf monsters/do not react to sound. 38 MTF_DORMANT = 0x0010, // The thing is dormant 39 MTF_GSINGLE = 0x0100, // Appearing in game modes 40 MTF_GCOOP = 0x0200, 41 MTF_GDEATHMATCH = 0x0400, 42 MTF_SHADOW = 0x0800, 43 MTF_ALTSHADOW = 0x1000, 44 MTF_FRIENDLY = 0x2000, 45 MTF_STANDSTILL = 0x4000, 46 47 MTF2_FIGHTER = 0x000010000, // Thing appearing in player classes 48 MTF2_CLERIC = 0x000020000, 49 MTF2_MAGE = 0x000040000; 50 51enum 52{ 53 pt_static, 54 pt_explode, 55 pt_explode2, 56 pt_ice_chunk, 57 pt_rail, 58 pt_fountain, 59 pt_spark 60}; 61 62// Flags for SectorDamage 63const int DAMAGE_PLAYERS = 1; 64const int DAMAGE_NONPLAYERS = 2; 65const int DAMAGE_IN_AIR = 4; 66const int DAMAGE_SUBCLASSES_PROTECT = 8; 67 68const int BODYQUESIZE = 32; 69const int CORPSEQUEUESIZE = 64; 70 71const int 72 SECF_SILENT = 1, 73 SECF_NOFALLINGDAMAGE = 2; 74 75name DefaultDoorSound; 76name DefaultCeilingSound; 77name DefaultSilentCeilingSound; 78name DefaultFloorSound; 79name DefaultFloorAltSound; 80name DefaultStairStepSound; 81name DefaultPlatformSound; 82 83bool bTeleportNewMapBothSides; 84bool bCheckStrifeStartSpots; 85bool bDefaultStopOnCrush; 86 87bool bPuffSpawned; 88 89int ExtPlayersBase; 90string Lock103Message; 91 92EntityEx CurrentSpeaker; 93EntityEx CurrentSpeakingTo; 94float OldSpeakerAngle; 95int CurrentSpeechIndex; 96bool ConversationSlideshow; 97 98EntityEx bodyque[BODYQUESIZE]; 99int BodyQueSize; 100int bodyqueslot; 101 102// Corpse queue for monsters 103EntityEx corpseQueue[CORPSEQUEUESIZE]; 104int CorpseQueSize; 105int corpseQueueSlot; 106 107//========================================================================== 108// 109// SpawnSpecials 110// 111//========================================================================== 112 113void SpawnSpecials() 114{ 115 sector_t *sector; 116 int i; 117 118 if (Level.bLightning) 119 { 120 LightningThinker Lightning = Spawn(LightningThinker); 121 Lightning.Init(); 122 } 123 124 // Init special SECTORs. 125 for (i = 0; i < XLevel.NumSectors; i++) 126 { 127 sector = &XLevel.Sectors[i]; 128 if (!sector->special) 129 { 130 continue; 131 } 132 if (sector->special & SECSPEC_SECRET_MASK) 133 { 134 // Secret sector. 135 TotalSecret++; 136 } 137 switch (sector->special & SECSPEC_BASE_MASK) 138 { 139 case SECSPEC_LightPhased: // Phased light 140 // Hardcoded base, use sector->lightlevel as the index 141 SpawnPhasedLight(sector, 80, -1); 142 sector->special &= ~SECSPEC_BASE_MASK; 143 break; 144 case SECSPEC_LightSequenceStart: // Phased light sequence start 145 SpawnLightSequence(sector, 1.0); 146 sector->special &= ~SECSPEC_BASE_MASK; 147 break; 148 // Specials 3 & 4 are used by the phased light sequences 149 case SECSPEC_LightFlicker: 150 SpawnLightFlash(sector); 151 sector->special &= ~SECSPEC_BASE_MASK; 152 break; 153 case SECSPEC_LightStrobeFast: 154 SpawnStrobeFlash(sector, FASTDARK, STROBEBRIGHT, 0); 155 sector->special &= ~SECSPEC_BASE_MASK; 156 break; 157 case SECSPEC_LightStrobeSlow: 158 SpawnStrobeFlash(sector, SLOWDARK, STROBEBRIGHT, 0); 159 sector->special &= ~SECSPEC_BASE_MASK; 160 break; 161 case SECSPEC_LightStrobeFastDamage: 162 SpawnStrobeFlash(sector, FASTDARK, STROBEBRIGHT, 0); 163 break; 164 case SECSPEC_LightGlow: 165 SpawnGlowingLight(sector); 166 sector->special &= ~SECSPEC_BASE_MASK; 167 break; 168 case SECSPEC_DoorCloseIn30: 169 SpawnDoorCloseIn30(sector); 170 break; 171 case SECSPEC_LightSyncStrobeSlow: 172 SpawnStrobeFlash(sector, SLOWDARK, STROBEBRIGHT, 1); 173 sector->special &= ~SECSPEC_BASE_MASK; 174 break; 175 case SECSPEC_LightSyncStrobeFast: 176 SpawnStrobeFlash(sector, FASTDARK, STROBEBRIGHT, 1); 177 sector->special &= ~SECSPEC_BASE_MASK; 178 break; 179 case SECSPEC_DoorRaiseIn5Minutes: 180 SpawnDoorRaiseIn5Mins(sector); 181 break; 182 case SECSPEC_LightFireFlicker: 183 SpawnFireFlicker(sector); 184 sector->special &= ~SECSPEC_BASE_MASK; 185 break; 186 case SECSPEC_ScrollEastLavaDamage: 187 SpawnScrollingFloor(sector, -1, 0, 3); 188 break; 189 case SECSPEC_ScrollNorthSlow: 190 case SECSPEC_ScrollNorthMedium: 191 case SECSPEC_ScrollNorthFast: 192 SpawnScrollingFloor(sector, 0, 1, (sector->special & 193 SECSPEC_BASE_MASK) - SECSPEC_ScrollNorthSlow); 194 break; 195 case SECSPEC_ScrollEastSlow: 196 case SECSPEC_ScrollEastMedium: 197 case SECSPEC_ScrollEastFast: 198 SpawnScrollingFloor(sector, -1, 0, (sector->special & 199 SECSPEC_BASE_MASK) - SECSPEC_ScrollEastSlow); 200 break; 201 case SECSPEC_ScrollSouthSlow: 202 case SECSPEC_ScrollSouthMedium: 203 case SECSPEC_ScrollSouthFast: 204 SpawnScrollingFloor(sector, 0, -1, (sector->special & 205 SECSPEC_BASE_MASK) - SECSPEC_ScrollSouthSlow); 206 break; 207 case SECSPEC_ScrollWestSlow: 208 case SECSPEC_ScrollWestMedium: 209 case SECSPEC_ScrollWestFast: 210 SpawnScrollingFloor(sector, 1, 0, (sector->special & 211 SECSPEC_BASE_MASK) - SECSPEC_ScrollWestSlow); 212 break; 213 case SECSPEC_ScrollNorthWestSlow: 214 case SECSPEC_ScrollNorthWestMedium: 215 case SECSPEC_ScrollNorthWestFast: 216 SpawnScrollingFloor(sector, 1, 1, (sector->special & 217 SECSPEC_BASE_MASK) - SECSPEC_ScrollNorthWestSlow); 218 break; 219 case SECSPEC_ScrollNorthEastSlow: 220 case SECSPEC_ScrollNorthEastMedium: 221 case SECSPEC_ScrollNorthEastFast: 222 SpawnScrollingFloor(sector, -1, 1, (sector->special & 223 SECSPEC_BASE_MASK) - SECSPEC_ScrollNorthEastSlow); 224 break; 225 case SECSPEC_ScrollSouthEastSlow: 226 case SECSPEC_ScrollSouthEastMedium: 227 case SECSPEC_ScrollSouthEastFast: 228 SpawnScrollingFloor(sector, -1, -1, (sector->special & 229 SECSPEC_BASE_MASK) - SECSPEC_ScrollSouthEastSlow); 230 break; 231 case SECSPEC_ScrollSouthWestSlow: 232 case SECSPEC_ScrollSouthWestMedium: 233 case SECSPEC_ScrollSouthWestFast: 234 SpawnScrollingFloor(sector, 1, -1, (sector->special & 235 SECSPEC_BASE_MASK) - SECSPEC_ScrollSouthWestSlow); 236 break; 237 case SECSPEC_ScrollEast5: 238 case SECSPEC_ScrollEast10: 239 case SECSPEC_ScrollEast25: 240 case SECSPEC_ScrollEast30: 241 case SECSPEC_ScrollEast35: 242 SpawnScrollingFloor(sector, -1, 0, (sector->special & 243 SECSPEC_BASE_MASK) - SECSPEC_ScrollEast5); 244 break; 245 } 246 } 247 248 // Init line EFFECTs 249 for (i = 0; i < XLevel.NumLines; i++) 250 { 251 switch (XLevel.Lines[i].special) 252 { 253 case LNSPEC_ScrollTextureLeft: 254 SpawnWallScroller(&XLevel.Lines[i], 1, 0); 255 XLevel.Lines[i].special = 0; 256 break; 257 case LNSPEC_ScrollTextureRight: 258 SpawnWallScroller(&XLevel.Lines[i], -1, 0); 259 XLevel.Lines[i].special = 0; 260 break; 261 case LNSPEC_ScrollTextureUp: 262 SpawnWallScroller(&XLevel.Lines[i], 0, 1); 263 XLevel.Lines[i].special = 0; 264 break; 265 case LNSPEC_ScrollTextureDown: 266 SpawnWallScroller(&XLevel.Lines[i], 0, -1); 267 XLevel.Lines[i].special = 0; 268 break; 269 case LNSPEC_ScrollTextureBoth: 270 SpawnTextureBothScroller(&XLevel.Lines[i]); 271 XLevel.Lines[i].special = 0; 272 break; 273 case LNSPEC_ScrollTextureModel: 274 SpawnScrollTextureModel(&XLevel.Lines[i]); 275 XLevel.Lines[i].special = 0; 276 break; 277 case LNSPEC_ScrollFloor: 278 SpawnScrollFloor(&XLevel.Lines[i]); 279 XLevel.Lines[i].special = 0; 280 break; 281 case LNSPEC_ScrollCeiling: 282 SpawnScrollCeiling(&XLevel.Lines[i]); 283 XLevel.Lines[i].special = 0; 284 break; 285 case LNSPEC_ScrollTextureOffsets: 286 SpawnWallOffsetsScroller(&XLevel.Lines[i]); 287 XLevel.Lines[i].special = 0; 288 break; 289 case LNSPEC_TransferWallLight: 290 SpawnTransferWallLight(&XLevel.Lines[i]); 291 XLevel.Lines[i].special = 0; 292 break; 293 } 294 } 295 296 SpawnPushers(); 297} 298 299//========================================================================== 300// 301// ActivateLine 302// 303//========================================================================== 304 305final bool ActivateLine(line_t* Line, EntityEx A, int Side, 306 int ActivationType) 307{ 308 int lineActivation; 309 bool repeat; 310 bool buttonSuccess; 311 bool changeBack; 312 313 if (!CheckActivation(ActivationType, Line, Side, A)) 314 { 315 return false; 316 } 317 318 lineActivation = Line->SpacFlags; 319 repeat = Line->flags & ML_REPEAT_SPECIAL; 320 buttonSuccess = ExecuteActionSpecial(Line->special, Line->arg1, 321 Line->arg2, Line->arg3, Line->arg4, Line->arg5, Line, Side, A); 322 changeBack = Line->special == LNSPEC_GlassBreak && 323 (Line->flags & ML_TWOSIDED) && buttonSuccess; 324 if ((lineActivation & (SPAC_Use | SPAC_Impact | SPAC_UseThrough)) && 325 buttonSuccess) 326 { 327 byte Quest; 328 ChangeSwitchTexture(Line->sidenum[0], repeat, 329 Line->special == LNSPEC_ExitNormal || 330 Line->special == LNSPEC_ExitSecret || 331 Line->special == LNSPEC_TeleportNewMap || 332 Line->special == LNSPEC_TeleportEndGame 333 ? 'switches/exitbutn' : 'switches/normbutn', Quest); 334 } 335 if (changeBack) 336 { 337 XLevel.Sides[Line->sidenum[1]].MidTexture = 338 XLevel.Sides[Line->sidenum[0]].MidTexture; 339 } 340 if (!repeat && buttonSuccess) 341 { 342 // clear the special on non-retriggerable lines 343 Line->special = 0; 344 } 345 return true; 346} 347 348//========================================================================== 349// 350// CheckActivation 351// 352//========================================================================== 353 354final bool CheckActivation(int activationType, line_t* line, int Side, 355 EntityEx A) 356{ 357 if ((line->flags & ML_FIRSTSIDEONLY) && Side == 1) 358 { 359 return false; 360 } 361 362 int lineActivation = line->SpacFlags; 363 if (lineActivation & SPAC_UseThrough) 364 { 365 lineActivation |= SPAC_Use; 366 } 367 else if (line->special == LNSPEC_Teleport && 368 (lineActivation & SPAC_Cross) && activationType == SPAC_PCross && 369 A && A.bMissile) 370 { 371 // Let missiles use regular player teleports 372 lineActivation |= SPAC_PCross; 373 } 374 // BOOM's generalized line types that allow monster use can actually be 375 // activated by anything except projectiles. 376 if (lineActivation & SPAC_AnyCross) 377 { 378 lineActivation |= SPAC_Cross | SPAC_MCross; 379 } 380 if (!(lineActivation & activationType)) 381 { 382 if (activationType == SPAC_Use && (lineActivation & SPAC_MUse) && 383 !A.bIsPlayer && A.bCanUseWalls) 384 { 385 return true; 386 } 387 if (activationType == SPAC_Push && (lineActivation & SPAC_MPush) && 388 !A.bIsPlayer && A.bActivatePushWall) 389 { 390 return true; 391 } 392 if (activationType != SPAC_MCross || !(lineActivation & SPAC_Cross)) 393 { 394 return false; 395 } 396 } 397 if (A && !A.bIsPlayer && !A.bMissile && 398 !(line->flags & ML_MONSTERSCANACTIVATE) && 399 (activationType != SPAC_MCross || !(lineActivation & SPAC_MCross))) 400 { 401 // With lax monster activation monsters can activate several line 402 // specials even when not marked as monster activateable. This is 403 // the default for non-Hexen maps in Hexen format. 404 if (!Level.bLaxMonsterActivation) 405 { 406 return false; 407 } 408 409 if ((activationType == SPAC_Use || activationType == SPAC_Push) && 410 (line->flags & ML_SECRET)) 411 { 412 return false; // never open secret doors 413 } 414 415 bool noway = true; 416 switch (activationType) 417 { 418 case SPAC_MCross: 419 if (!(lineActivation & SPAC_MCross)) 420 { 421 switch (line->special) 422 { 423 case LNSPEC_DoorRaise: 424 if (line->arg2 >= 64) 425 { 426 break; 427 } 428 case LNSPEC_PlatDownWaitUpStay: 429 case LNSPEC_Teleport: 430 case LNSPEC_TeleportNoFog: 431 case LNSPEC_PlatDownWaitUpStayLip: 432 case LNSPEC_TeleportLine: 433 noway = false; 434 } 435 } 436 else 437 { 438 noway = false; 439 } 440 break; 441 442 case SPAC_Use: 443 case SPAC_Push: 444 switch (line->special) 445 { 446 case LNSPEC_DoorRaise: 447 if (line->arg1 == 0 && line->arg2 < 64) 448 { 449 noway = false; 450 } 451 break; 452 case LNSPEC_Teleport: 453 case LNSPEC_TeleportNoFog: 454 noway = false; 455 } 456 break; 457 458 default: 459 noway = false; 460 break; 461 } 462 return !noway; 463 } 464 if (activationType == SPAC_MCross && !(lineActivation & SPAC_MCross) && 465 !(line->flags & ML_MONSTERSCANACTIVATE)) 466 { 467 return false; 468 } 469 return true; 470} 471 472//========================================================================== 473// 474// ExecuteActionSpecial 475// 476//========================================================================== 477 478int ExecuteActionSpecial(int Special, int Arg1, int Arg2, int Arg3, 479 int Arg4, int Arg5, line_t* Line, int Side, Entity E) 480{ 481 EntityEx A = EntityEx(E); 482 int buttonSuccess = false; 483 switch (Special) 484 { 485 case LNSPEC_PolyStartLine: 486 break; 487 case LNSPEC_PolyRotateLeft: 488 buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 1, 489 false); 490 break; 491 case LNSPEC_PolyRotateRight: 492 buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, -1, 493 false); 494 break; 495 case LNSPEC_PolyMove: 496 buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 497 false, false); 498 break; 499 case LNSPEC_PolyExplicitLine: // Only used in initialization 500 break; 501 case LNSPEC_PolyMoveTimes8: 502 buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, true, 503 false); 504 break; 505 case LNSPEC_PolyDoorSwing: 506 buttonSuccess = EV_OpenPolyDoor(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 507 PolyobjDoor::PODOOR_SWING); 508 break; 509 case LNSPEC_PolyDoorSlide: 510 buttonSuccess = EV_OpenPolyDoor(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 511 PolyobjDoor::PODOOR_SLIDE); 512 break; 513 case LNSPEC_DoorClose: 514 buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5, 515 VerticalDoor::DOOREV_Close, Line, A); 516 break; 517 case LNSPEC_DoorOpen: 518 buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5, 519 VerticalDoor::DOOREV_Open, Line, A); 520 break; 521 case LNSPEC_DoorRaise: 522 buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5, 523 VerticalDoor::DOOREV_Raise, Line, A); 524 break; 525 case LNSPEC_DoorLockedRaise: 526 if (CheckLock(A, Arg4, true)) 527 { 528 buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5, 529 VerticalDoor::DOOREV_RaiseLocked, Line, A); 530 } 531 break; 532 case LNSPEC_DoorAnimated: 533 buttonSuccess = EV_TextureChangeDoor(Arg1, Arg2, Arg3, Arg4, Arg5, 534 Line, A); 535 break; 536 case LNSPEC_Autosave: 537 AutoSave(); 538 buttonSuccess = true; 539 break; 540 case LNSPEC_ThingRaise: 541 buttonSuccess = EV_ThingRaise(Arg1, Arg2, Arg3, Arg4, Arg5, A); 542 break; 543 case LNSPEC_StartConversation: 544 buttonSuccess = EV_StartConversation(Arg1, Arg2, Arg3, Arg4, Arg5, A); 545 break; 546 case LNSPEC_ThingStop: 547 buttonSuccess = EV_ThingStop(Arg1, Arg2, Arg3, Arg4, Arg5, A); 548 break; 549 case LNSPEC_FloorLowerByValue: 550 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 551 FloorMover::FLOOREV_LowerByValue, Line); 552 break; 553 case LNSPEC_FloorLowerToLowest: 554 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 555 FloorMover::FLOOREV_LowerToLowest, Line); 556 break; 557 case LNSPEC_FloorLowerToNearest: 558 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 559 FloorMover::FLOOREV_LowerToNearest, Line); 560 break; 561 case LNSPEC_FloorRaiseByValue: 562 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 563 FloorMover::FLOOREV_RaiseByValue, Line); 564 break; 565 case LNSPEC_FloorRaiseToHighest: 566 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 567 FloorMover::FLOOREV_RaiseToHighest, Line); 568 break; 569 case LNSPEC_FloorRaiseToNearest: 570 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 571 FloorMover::FLOOREV_RaiseToNearest, Line); 572 break; 573 case LNSPEC_StairsBuildDownNormal: 574 buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5, 575 StairStepMover::STAIRSEV_DownNormal); 576 break; 577 case LNSPEC_StairsBuildUpNormal: 578 buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5, 579 StairStepMover::STAIRSEV_UpNormal); 580 break; 581 case LNSPEC_FloorRaiseAndCrush: 582 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 583 FloorMover::FLOOREV_RaiseAndCrush, Line); 584 break; 585 case LNSPEC_PillarBuild: // (no crushing) 586 buttonSuccess = EV_BuildPillar(Arg1, Arg2, Arg3, Arg4, Arg5, false); 587 break; 588 case LNSPEC_PillarOpen: 589 buttonSuccess = EV_OpenPillar(Arg1, Arg2, Arg3, Arg4, Arg5); 590 break; 591 case LNSPEC_StairsBuildDownSync: 592 buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5, 593 StairStepMover::STAIRSEV_DownSync); 594 break; 595 case LNSPEC_StairsBuildUpSync: 596 buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5, 597 StairStepMover::STAIRSEV_UpSync); 598 break; 599 case LNSPEC_ForceField: 600 buttonSuccess = EV_ForceField(Arg1, Arg2, Arg3, Arg4, Arg5, A); 601 break; 602 case LNSPEC_ClearForceField: 603 buttonSuccess = EV_RemoveForceField(Arg1, Arg2, Arg3, Arg4, Arg5); 604 break; 605 case LNSPEC_FloorRaiseByValueTimes8: 606 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 607 FloorMover::FLOOREV_RaiseByValueTimes8, Line); 608 break; 609 case LNSPEC_FloorLowerByValueTimes8: 610 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 611 FloorMover::FLOOREV_LowerByValueTimes8, Line); 612 break; 613 case LNSPEC_FloorMoveToValue: 614 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 615 FloorMover::FLOOREV_MoveToValue, Line); 616 break; 617 case LNSPEC_CeilingWaggle: 618 buttonSuccess = EV_StartCeilingWaggle(Arg1, Arg2, Arg3, Arg4, Arg5); 619 break; 620 case LNSPEC_TeleportZombieChanger: 621 if (Side == 0) 622 { 623 // Only teleport when crossing the front side of a line 624 buttonSuccess = EV_Teleport(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line, 625 true); 626 A.SetState(A.FindState('Pain')); 627 } 628 break; 629 case LNSPEC_CeilingLowerByValue: 630 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 631 CeilingMover::CEILEV_LowerByValue, Line); 632 break; 633 case LNSPEC_CeilingRaiseByValue: 634 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 635 CeilingMover::CEILEV_RaiseByValue, Line); 636 break; 637 case LNSPEC_CeilingCrushAndRaise: 638 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 639 CeilingMover::CEILEV_CrushAndRaise, Line); 640 break; 641 case LNSPEC_CeilingLowerAndCrush: 642 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 643 CeilingMover::CEILEV_LowerAndCrush, Line); 644 break; 645 case LNSPEC_CeilingCrushStop: 646 buttonSuccess = EV_CeilingCrushStop(Line, Arg1, Arg2, Arg3, Arg4, 647 Arg5); 648 break; 649 case LNSPEC_CeilingCrushRaiseAndStay: 650 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 651 CeilingMover::CEILEV_CrushRaiseAndStay, Line); 652 break; 653 case LNSPEC_FloorCrushStop: 654 buttonSuccess = EV_FloorCrushStop(Arg1, Arg2, Arg3, Arg4, Arg5); 655 break; 656 case LNSPEC_CeilingMoveToValue: 657 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 658 CeilingMover::CEILEV_MoveToValue, Line); 659 break; 660 case LNSPEC_GlassBreak: 661 buttonSuccess = EV_GlassBreak(Arg1, Arg2, Arg3, Arg4, Arg5, Line, A); 662 break; 663 case LNSPEC_ScrollWall: 664 buttonSuccess = EV_ScrollWall(Arg1, Arg2, Arg3, Arg4, Arg5); 665 break; 666 case LNSPEC_LineSetTextureOffset: 667 buttonSuccess = EV_LineSetTextureOffset(Arg1, Arg2, Arg3, Arg4, Arg5); 668 break; 669 case LNSPEC_SectorChangeFlags: 670 buttonSuccess = EV_SectorChangeFlags(Arg1, Arg2, Arg3, Arg4, Arg5); 671 break; 672 case LNSPEC_PlatPerpetualRaise: 673 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 674 Platform::PLATEV_PerpetualRaise, Line); 675 break; 676 case LNSPEC_PlatStop: 677 buttonSuccess = EV_StopPlat(Arg1, Arg2, Arg3, Arg4, Arg5); 678 break; 679 case LNSPEC_PlatDownWaitUpStay: 680 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 681 Platform::PLATEV_DownWaitUpStay, Line); 682 break; 683 case LNSPEC_PlatDownByValueWaitUpStay: 684 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 685 Platform::PLATEV_DownByValueWaitUpStay, Line); 686 break; 687 case LNSPEC_PlatUpWaitDownStay: 688 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 689 Platform::PLATEV_UpWaitDownStay, Line); 690 break; 691 case LNSPEC_PlatUpByValueWaitDownStay: 692 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 693 Platform::PLATEV_UpByValueWaitDownStay, Line); 694 break; 695 case LNSPEC_FloorLowerTimes8Instant: 696 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 697 FloorMover::FLOOREV_LowerTimes8Instant, Line); 698 break; 699 case LNSPEC_FloorRaiseTimes8Instant: 700 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 701 FloorMover::FLOOREV_RaiseTimes8Instant, Line); 702 break; 703 case LNSPEC_FloorMoveToValueTimes8: 704 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 705 FloorMover::FLOOREV_MoveToValueTimes8, Line); 706 break; 707 case LNSPEC_CeilingMoveToValueTimes8: 708 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 709 CeilingMover::CEILEV_MoveToValueTimes8, Line); 710 break; 711 case LNSPEC_Teleport: 712 if (Side == 0) 713 { 714 // Only teleport when crossing the front side of a line 715 buttonSuccess = EV_Teleport(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line, 716 true); 717 } 718 break; 719 case LNSPEC_TeleportNoFog: 720 if (Side == 0) 721 { 722 // Only teleport when crossing the front side of a line 723 buttonSuccess = EV_Teleport(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line, 724 false); 725 } 726 break; 727 case LNSPEC_ThrustThing: 728 if (A && !Side) // Only thrust on side 0 729 { 730 A.Thrust(itof(Arg1) * (90.0 / 64.0), itof(Arg2)); 731 buttonSuccess = 1; 732 } 733 break; 734 case LNSPEC_DamageThing: 735 if (A) 736 { 737 if (Arg1) 738 { 739 A.Damage(none, none, Arg1, ModToDamageType(Arg2)); 740 } 741 else 742 { 743 // If arg1 is zero, then guarantee a kill 744 A.Damage(none, none, 10000, ModToDamageType(Arg2)); 745 } 746 } 747 buttonSuccess = 1; 748 break; 749 case LNSPEC_TeleportNewMap: 750 if (Side == 0 || bTeleportNewMapBothSides) 751 { 752 // Only teleport when crossing the front side of a line 753 // Players must be alive to teleport 754 if (!(A && A.bIsPlayer && A.Player.PlayerState == PST_DEAD)) 755 { 756 Completed(Arg1, Arg2, Arg3); 757 buttonSuccess = true; 758 } 759 } 760 break; 761 case LNSPEC_TeleportEndGame: 762 if (Side == 0) 763 { 764 // Only teleport when crossing the front side of a line 765 // Players must be alive to teleport 766 if (!(A && A.bIsPlayer && A.Player.PlayerState == PST_DEAD)) 767 { 768 buttonSuccess = true; 769 if (Game.deathmatch) 770 { 771 // Winning in deathmatch just goes back to map 1 772 Completed(1, 0, 0); 773 } 774 else 775 { 776 // Passing -1, -1 to G_Completed() starts the Finale 777 Completed(-1, -1, 0); 778 } 779 } 780 } 781 break; 782 case LNSPEC_TeleportOther: 783 buttonSuccess = EV_TeleportOther(Arg1, Arg2, Arg3, Arg4, Arg5); 784 break; 785 case LNSPEC_TeleportGroup: 786 buttonSuccess = EV_TeleportGroup(Arg1, Arg2, Arg3, Arg4, Arg5, A); 787 break; 788 case LNSPEC_TeleportSector: 789 buttonSuccess = EV_TeleportSector(Arg1, Arg2, Arg3, Arg4, Arg5); 790 break; 791 case LNSPEC_ACSExecute: 792 buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line, 793 Side, false, false); 794 break; 795 case LNSPEC_ACSSuspend: 796 buttonSuccess = XLevel.SuspendACS(Arg1, Arg2); 797 break; 798 case LNSPEC_ACSTerminate: 799 buttonSuccess = XLevel.TerminateACS(Arg1, Arg2); 800 break; 801 case LNSPEC_ACSLockedExecute: 802 if (CheckLock(A, Arg5, false)) 803 { 804 buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, 0, A, 805 Line, Side, false, false); 806 } 807 break; 808 case LNSPEC_ACSExecuteWithResult: 809 buttonSuccess = XLevel.StartACS(Arg1, 0, Arg2, Arg3, Arg4, A, Line, 810 Side, true, true); 811 break; 812 case LNSPEC_ACSLockedExecuteDoor: 813 if (CheckLock(A, Arg5, true)) 814 { 815 buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, 0, A, 816 Line, Side, false, false); 817 } 818 break; 819 case LNSPEC_PolyRotateLeftOverride: 820 buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 1, 821 true); 822 break; 823 case LNSPEC_PolyRotateRightOverride: 824 buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, -1, 825 true); 826 break; 827 case LNSPEC_PolyMoveOverride: 828 buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 829 false, true); 830 break; 831 case LNSPEC_PolyMoveTimes8Override: 832 buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 833 true, true); 834 break; 835 case LNSPEC_PillarBuildCrush: 836 buttonSuccess = EV_BuildPillar(Arg1, Arg2, Arg3, Arg4, Arg5, true); 837 break; 838 case LNSPEC_FloorAndCeilingLowerByValue: 839 buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5, 840 Elevator::ELEVEV_Lower, Line); 841 break; 842 case LNSPEC_FloorAndCeilingRaiseByValue: 843 buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5, 844 Elevator::ELEVEV_Raise, Line); 845 break; 846 case LNSPEC_LightForceLightning: 847 buttonSuccess = true; 848 ForceLightning(Arg1); 849 break; 850 case LNSPEC_LightRaiseByValue: 851 buttonSuccess = EV_LightRaiseByValue(Arg1, Arg2, Arg3, Arg4, Arg5); 852 break; 853 case LNSPEC_LightLowerByValue: 854 buttonSuccess = EV_LightLowerByValue(Arg1, Arg2, Arg3, Arg4, Arg5); 855 break; 856 case LNSPEC_LightChangeToValue: 857 buttonSuccess = EV_LightChangeToValue(Arg1, Arg2, Arg3, Arg4, Arg5); 858 break; 859 case LNSPEC_LightFade: 860 buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5, 861 LightEffect::LIGHTEV_Fade); 862 break; 863 case LNSPEC_LightGlow: 864 buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5, 865 LightEffect::LIGHTEV_Glow); 866 break; 867 case LNSPEC_LightFlicker: 868 buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5, 869 LightEffect::LIGHTEV_Flicker); 870 break; 871 case LNSPEC_LightStrobe: 872 buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5, 873 LightEffect::LIGHTEV_Strobe); 874 break; 875 case LNSPEC_LightStop: 876 buttonSuccess = EV_LightStop(Arg1); 877 break; 878 case LNSPEC_ThingDamage: 879 buttonSuccess = EV_ThingDamage(Arg1, Arg2, Arg3, Arg4, Arg5, A); 880 break; 881 case LNSPEC_QuakeTremor: 882 buttonSuccess = A_LocalQuake(Arg1, Arg2, Arg3, Arg4, Arg5); 883 break; 884 case LNSPEC_ThingMove: 885 buttonSuccess = EV_ThingMove(Arg1, Arg2, Arg3, Arg4, Arg5, A); 886 break; 887 case LNSPEC_ThingSetSpecial: 888 buttonSuccess = EV_ThingSetSpecial(Arg1, Arg2, Arg3, Arg4, Arg5, A); 889 break; 890 case LNSPEC_ThrustThingZ: 891 buttonSuccess = EV_ThrustThingZ(Arg1, Arg2, Arg3, Arg4, Arg5, A); 892 break; 893 case LNSPEC_UsePuzzleItem: 894 buttonSuccess = EV_LineSearchForPuzzleItem(Arg1, Arg2, Arg3, Arg4, 895 Arg5, A); 896 break; 897 case LNSPEC_ThingActivate: 898 buttonSuccess = EV_ThingActivate(Arg1, A); 899 break; 900 case LNSPEC_ThingDeactivate: 901 buttonSuccess = EV_ThingDeactivate(Arg1, A); 902 break; 903 case LNSPEC_ThingRemove: 904 buttonSuccess = EV_ThingRemove(Arg1); 905 break; 906 case LNSPEC_ThingDestroy: 907 buttonSuccess = EV_ThingDestroy(Arg1, Arg2); 908 break; 909 case LNSPEC_ThingProjectile: 910 buttonSuccess = EV_ThingProjectile(Arg1, Arg2, Arg3, Arg4, Arg5, 0, 911 0, '', A); 912 break; 913 case LNSPEC_ThingSpawn: 914 buttonSuccess = EV_ThingSpawn(Arg1, Arg2, Arg3, Arg4, Arg5, true, 915 false, A); 916 break; 917 case LNSPEC_ThingProjectileGravity: 918 buttonSuccess = EV_ThingProjectile(Arg1, Arg2, Arg3, Arg4, Arg5, 1, 919 0, '', A); 920 break; 921 case LNSPEC_ThingSpawnNoFog: 922 buttonSuccess = EV_ThingSpawn(Arg1, Arg2, Arg3, Arg4, Arg5, false, 923 false, A); 924 break; 925 case LNSPEC_FloorWaggle: 926 buttonSuccess = EV_StartFloorWaggle(Arg1, Arg2, Arg3, Arg4, Arg5); 927 break; 928 case LNSPEC_ThingSpawnFacing: 929 buttonSuccess = EV_ThingSpawn(Arg1, Arg2, 0, Arg4, Arg5, !Arg3, 930 true, A); 931 break; 932 case LNSPEC_SectorSoundChange: 933 buttonSuccess = EV_SectorSoundChange(Arg1, Arg2, Arg3, Arg4, Arg5); 934 break; 935 case LNSPEC_SectorSetPlaneReflection: 936 buttonSuccess = EV_SectorSetPlaneReflection(Arg1, Arg2, Arg3, Arg4, 937 Arg5); 938 break; 939 case LNSPEC_CeilingGenericCrush2: 940 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 941 CeilingMover::CEILEV_GenericCrush2, Line); 942 break; 943 case LNSPEC_SectorSetCeilingScale2: 944 buttonSuccess = EV_SectorSetCeilingScale2(Arg1, Arg2, Arg3, Arg4, 945 Arg5); 946 break; 947 case LNSPEC_SectorSetFloorScale2: 948 buttonSuccess = EV_SectorSetFloorScale2(Arg1, Arg2, Arg3, Arg4, 949 Arg5); 950 break; 951 case LNSPEC_PlaneUpNearestWaitDownStay: 952 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 953 Platform::PLATEV_UpNearestWaitDownStay, Line); 954 break; 955 case LNSPEC_NoiseAlert: 956 buttonSuccess = EV_NoiseAlert(A, Arg1, Arg2, Arg3, Arg4, Arg5); 957 break; 958 case LNSPEC_SendToCommunicator: 959 buttonSuccess = EV_SendToCommunicator(A, Arg1, Arg2, Arg3, Arg4, 960 Arg5, Side); 961 break; 962 case LNSPEC_ThingProjectileIntercept: 963 buttonSuccess = EV_ThingProjectileIntercept(Arg1, Arg2, Arg3, Arg4, 964 Arg5, A); 965 break; 966 case LNSPEC_ThingChangeTID: 967 buttonSuccess = EV_ThingChangeTID(Arg1, Arg2, Arg3, Arg4, Arg5, A); 968 break; 969 case LNSPEC_ThingHate: 970 buttonSuccess = EV_ThingHate(Arg1, Arg2, Arg3, Arg4, Arg5, A); 971 break; 972 case LNSPEC_ThingProjectileAimed: 973 buttonSuccess = EV_ThingProjectileAimed(Arg1, Arg2, Arg3, Arg4, Arg5, 974 A); 975 break; 976 case LNSPEC_ChangeSkill: 977 LineSpecialGameInfo(Game).SetSkill(Arg1); 978 buttonSuccess = true; 979 break; 980 case LNSPEC_ThingSetTranslation: 981 buttonSuccess = EV_ThingSetTranslation(Arg1, Arg2, Arg3, Arg4, Arg5, 982 A); 983 break; 984 case LNSPEC_LineAlignCeiling: 985 buttonSuccess = EV_LineAlignCeiling(Arg1, Arg2, Arg3, Arg4, Arg5); 986 break; 987 case LNSPEC_LineAlignFloor: 988 buttonSuccess = EV_LineAlignFloor(Arg1, Arg2, Arg3, Arg4, Arg5); 989 break; 990 case LNSPEC_SectorSetRotation: 991 buttonSuccess = EV_SectorSetRotation(Arg1, Arg2, Arg3, Arg4, Arg5); 992 break; 993 case LNSPEC_SectorSetCeilingPanning: 994 buttonSuccess = EV_SectorSetCeilingPanning(Arg1, Arg2, Arg3, Arg4, 995 Arg5); 996 break; 997 case LNSPEC_SectorSetFloorPanning: 998 buttonSuccess = EV_SectorSetFloorPanning(Arg1, Arg2, Arg3, Arg4, 999 Arg5); 1000 break; 1001 case LNSPEC_SectorSetCeilingScale: 1002 buttonSuccess = EV_SectorSetCeilingScale(Arg1, Arg2, Arg3, Arg4, 1003 Arg5); 1004 break; 1005 case LNSPEC_SectorSetFloorScale: 1006 buttonSuccess = EV_SectorSetFloorScale(Arg1, Arg2, Arg3, Arg4, 1007 Arg5); 1008 break; 1009 case LNSPEC_SetPlayerProperty: 1010 buttonSuccess = EV_SetPlayerProperty(Arg1, Arg2, Arg3, Arg4, Arg5, A); 1011 break; 1012 case LNSPEC_CeilingLowerToHighestFloor: 1013 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1014 CeilingMover::CEILEV_LowerToHighestFloor, Line); 1015 break; 1016 case LNSPEC_CeilingLowerInstant: 1017 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1018 CeilingMover::CEILEV_LowerTimes8Instant, Line); 1019 break; 1020 case LNSPEC_CeilingRaiseInstant: 1021 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1022 CeilingMover::CEILEV_RaiseTimes8Instant, Line); 1023 break; 1024 case LNSPEC_CeilingCrushRaiseAndStayA: 1025 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1026 CeilingMover::CEILEV_CrushRaiseAndStayA, Line); 1027 break; 1028 case LNSPEC_CeilingCrushAndRaiseA: 1029 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1030 CeilingMover::CEILEV_CrushAndRaiseA, Line); 1031 break; 1032 case LNSPEC_CeilingCrushAndRaiseSilentA: 1033 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1034 CeilingMover::CEILEV_CrushAndRaiseSilA, Line); 1035 break; 1036 case LNSPEC_CeilingRaiseByValueTimes8: 1037 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1038 CeilingMover::CEILEV_RaiseByValueTimes8, Line); 1039 break; 1040 case LNSPEC_CeilingLowerByValueTimes8: 1041 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1042 CeilingMover::CEILEV_LowerByValueTimes8, Line); 1043 break; 1044 case LNSPEC_FloorGeneric: 1045 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1046 FloorMover::FLOOREV_Generic, Line); 1047 break; 1048 case LNSPEC_CeilingGeneric: 1049 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1050 CeilingMover::CEILEV_Generic, Line); 1051 break; 1052 case LNSPEC_DoorGeneric: 1053 buttonSuccess = EV_GenericDoor(Arg1, Arg2, Arg3, Arg4, Arg5, Line, A); 1054 break; 1055 case LNSPEC_PlatGeneric: 1056 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 1057 Platform::PLATEV_Generic, Line); 1058 break; 1059 case LNSPEC_StairsGeneric: 1060 buttonSuccess = EV_BuildStairsOld(Arg1, Arg2, Arg3, Arg4, Arg5, 1061 true, Line); 1062 break; 1063 case LNSPEC_CeilingGenericCrush: 1064 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1065 CeilingMover::CEILEV_GenericCrush, Line); 1066 break; 1067 case LNSPEC_PlatDownWaitUpStayLip: 1068 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 1069 Platform::PLATEV_DownWaitUpStayLip, Line); 1070 break; 1071 case LNSPEC_PlatPerpetualRaiseLip: 1072 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 1073 Platform::PLATEV_PerpetualRaiseLip, Line); 1074 break; 1075 case LNSPEC_LineTranslucent: 1076 buttonSuccess = EV_LineTranslucent(Arg1, Arg2, Arg3, Arg4, Arg5); 1077 break; 1078 case LNSPEC_SectorSetColour: 1079 buttonSuccess = EV_SectorSetColour(Arg1, Arg2, Arg3, Arg4, Arg5); 1080 break; 1081 case LNSPEC_SectorSetFade: 1082 buttonSuccess = EV_SectorSetFade(Arg1, Arg2, Arg3, Arg4, Arg5); 1083 break; 1084 case LNSPEC_SectorSetDamage: 1085 buttonSuccess = EV_SectorSetDamage(Arg1, Arg2, Arg3, Arg4, Arg5); 1086 break; 1087 case LNSPEC_TeleportLine: 1088 buttonSuccess = EV_SilentLineTeleport(Line, Side, A, Arg2, Arg3); 1089 break; 1090 case LNSPEC_SectorSetGravity: 1091 buttonSuccess = EV_SectorSetGravity(Arg1, Arg2, Arg3, Arg4, Arg5); 1092 break; 1093 case LNSPEC_StairsBuildUpDoom: 1094 buttonSuccess = EV_BuildStairsOld(Arg1, Arg2, Arg3, Arg4, Arg5, 1095 false, Line); 1096 break; 1097 case LNSPEC_SectorSetWind: 1098 buttonSuccess = AdjustPusher(Arg1, Arg2, Arg3, Arg4, Arg5, Line, 1099 Pusher::PUSHER_Wind); 1100 break; 1101 case LNSPEC_SectorSetFriction: 1102 buttonSuccess = EV_SectorSetFriction(Arg1, Arg2, Arg3, Arg4, Arg5); 1103 break; 1104 case LNSPEC_SectorSetCurrent: 1105 buttonSuccess = AdjustPusher(Arg1, Arg2, Arg3, Arg4, Arg5, Line, 1106 Pusher::PUSHER_Current); 1107 break; 1108 case LNSPEC_ScrollTextureBoth: 1109 buttonSuccess = EV_ScrollTextureBoth(Arg1, Arg2, Arg3, Arg4, Arg5); 1110 break; 1111 case LNSPEC_ScrollFloor: 1112 buttonSuccess = EV_ScrollFloor(Arg1, Arg2, Arg3, Arg4, Arg5); 1113 break; 1114 case LNSPEC_ScrollCeiling: 1115 buttonSuccess = EV_ScrollCeiling(Arg1, Arg2, Arg3, Arg4, Arg5); 1116 break; 1117 case LNSPEC_ACSExecuteAlways: 1118 buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, Arg5, A, 1119 Line, Side, true, false); 1120 break; 1121 case LNSPEC_FloorRaiseToNearestChange: 1122 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1123 FloorMover::FLOOREV_RaiseToNearestChange, Line); 1124 break; 1125 case LNSPEC_ThingSetGoal: 1126 buttonSuccess = EV_ThingSetGoal(Arg1, Arg2, Arg3, Arg4, Arg5); 1127 break; 1128 case LNSPEC_FloorRaiseByValueChangeTex: 1129 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1130 FloorMover::FLOOREV_RaiseByValueChange2, Line); 1131 break; 1132 case LNSPEC_PlatToggle: 1133 buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5, 1134 Platform::PLATEV_Toggle, Line); 1135 break; 1136 case LNSPEC_LightStrobeDoom: 1137 buttonSuccess = EV_StartLightStrobing(Arg1, Arg2, Arg3, Arg4, Arg5); 1138 break; 1139 case LNSPEC_LightMinNeighbor: 1140 buttonSuccess = EV_TurnTagLightsOff(Arg1, Arg2, Arg3, Arg4, Arg5); 1141 break; 1142 case LNSPEC_LightMaxNeighbor: 1143 buttonSuccess = EV_TagLightTurnOn(Arg1, Arg2, Arg3, Arg4, Arg5); 1144 break; 1145 case LNSPEC_FloorTransferTrigger: 1146 buttonSuccess = EV_FloorTransferTrigger(Arg1, Arg2, Arg3, Arg4, Arg5, 1147 Line); 1148 break; 1149 case LNSPEC_FloorTransferNumeric: 1150 buttonSuccess = EV_FloorTransferNumeric(Arg1, Arg2, Arg3, Arg4, Arg5); 1151 break; 1152 case LNSPEC_ChangeCamera: 1153 buttonSuccess = EV_ChangeCamera(Arg1, Arg2, Arg3, Arg4, Arg5, A); 1154 break; 1155 case LNSPEC_FloorRaiseToLowestCeiling: 1156 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1157 FloorMover::FLOOREV_RaiseToLowestCeiling, Line); 1158 break; 1159 case LNSPEC_FloorRaiseByValueChange: 1160 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1161 FloorMover::FLOOREV_RaiseByValueChange, Line); 1162 break; 1163 case LNSPEC_FloorRaiseByTexture: 1164 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1165 FloorMover::FLOOREV_RaiseByTexture, Line); 1166 break; 1167 case LNSPEC_FloorLowerToLowestChange: 1168 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1169 FloorMover::FLOOREV_LowerToLowestChange, Line); 1170 break; 1171 case LNSPEC_FloorLowerToHighest: 1172 buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5, 1173 FloorMover::FLOOREV_LowerToHighest, Line); 1174 break; 1175 case LNSPEC_ExitNormal: 1176 buttonSuccess = true; 1177 ExitLevel(Arg1); 1178 break; 1179 case LNSPEC_ExitSecret: 1180 buttonSuccess = true; 1181 SecretExitLevel(Arg1); 1182 break; 1183 case LNSPEC_ElevatorRaiseToNearest: 1184 buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5, 1185 Elevator::ELEVEV_Up, Line); 1186 break; 1187 case LNSPEC_ElevatorMoveToFloor: 1188 buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5, 1189 Elevator::ELEVEV_Current, Line); 1190 break; 1191 case LNSPEC_ElevatorLowerToNearest: 1192 buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5, 1193 Elevator::ELEVEV_Down, Line); 1194 break; 1195 case LNSPEC_HealThing: 1196 buttonSuccess = EV_HealThing(Arg1, Arg2, Arg3, Arg4, Arg5, A); 1197 break; 1198 case LNSPEC_DoorCloseWaitOpen: 1199 buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5, 1200 VerticalDoor::DOOREV_CloseWaitOpen, Line, A); 1201 break; 1202 case LNSPEC_FloorDonut: 1203 buttonSuccess = EV_DoDonut(Arg1, Arg2, Arg3, Arg4, Arg5); 1204 break; 1205 case LNSPEC_FloorAndCeilingLowerRaise: 1206 buttonSuccess = EV_DoCeiling(Arg1, Arg3, 0, 0, 0, 1207 CeilingMover::CEILEV_RaiseToHighest, Line); 1208 buttonSuccess |= EV_DoFloor(Arg1, Arg2, 0, 0, 0, 1209 FloorMover::FLOOREV_LowerToLowest, Line); 1210 break; 1211 case LNSPEC_CeilingRaiseToNearest: 1212 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1213 CeilingMover::CEILEV_RaiseToNearest, Line); 1214 break; 1215 case LNSPEC_CeilingLowerToLowest: 1216 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1217 CeilingMover::CEILEV_LowerToLowest, Line); 1218 break; 1219 case LNSPEC_CeilingLowerToFloor: 1220 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1221 CeilingMover::CEILEV_LowerToFloor, Line); 1222 break; 1223 case LNSPEC_CeilingCrushRaiseAndStaySilentA: 1224 buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5, 1225 CeilingMover::CEILEV_CrushRaiseAndStaySilA, Line); 1226 break; 1227 1228 // Line specials only processed during level initialization 1229 // LNSPEC_ScrollTextureLeft: 1230 // LNSPEC_ScrollTextureRight: 1231 // LNSPEC_ScrollTextureUp: 1232 // LNSPEC_ScrollTextureDown: 1233 // LNSPEC_LineSetIdentification: 1234 // LNSPEC_3DFloor: 1235 // LNSPEC_Contents: 1236 // LNSPEC_PlaneAlign: 1237 // LNSPEC_TransferHeights: 1238 // LNSPEC_ScrollTextureModel: 1239 // LNSPEC_ScrollTextureOffsets: 1240 // LNSPEC_PointPushSetForce: 1241 1242 default: 1243 // Log everything else to know what needs to be implemented. 1244 print("Unknown action special %d(%d, %d, %d, %d, %d)", Special, 1245 Arg1, Arg2, Arg3, Arg4, Arg5); 1246 break; 1247 } 1248 return buttonSuccess; 1249} 1250 1251//========================================================================== 1252// 1253// StartPlaneWatcher 1254// 1255//========================================================================== 1256 1257final void StartPlaneWatcher(Entity it, line_t* line, int lineSide, 1258 bool ceiling, int tag, int height, int special, int arg0, int arg1, 1259 int arg2, int arg3, int arg4) 1260{ 1261 PlaneWatcher PW; 1262 1263 PW = Spawn(PlaneWatcher); 1264 PW.Start(it, line, lineSide, ceiling, tag, height, special, arg0, arg1, 1265 arg2, arg3, arg4); 1266} 1267 1268//************************************************************************** 1269// 1270// Doors 1271// 1272//************************************************************************** 1273 1274//========================================================================== 1275// 1276// EV_DoDoor 1277// 1278// Move a door up/down 1279// 1280//========================================================================== 1281 1282final int EV_DoDoor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1283 int Type, line_t* Line, Entity Thing) 1284{ 1285 int SecNum; 1286 int RetCode; 1287 sector_t* Sec; 1288 VerticalDoor Door; 1289 1290 RetCode = false; 1291 if (!Arg1) 1292 { 1293 if (!Line) 1294 return false; 1295 1296 // Make sure it's a two-sided line. 1297 if (Line->sidenum[1] < 0) 1298 return false; 1299 1300 // if the sector has an active thinker, use it 1301 Sec = XLevel.Sides[Line->sidenum[1]].Sector; 1302 if (Sec->CeilingData) 1303 { 1304 Door = VerticalDoor(Sec->CeilingData); 1305 if (Door) 1306 { 1307 return Door.ReUse(Type, Line, Thing); 1308 } 1309 return false; 1310 } 1311 1312 // new door thinker 1313 Door = Spawn(VerticalDoor); 1314 Door.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type); 1315 RetCode = true; 1316 } 1317 else 1318 { 1319 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 1320 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 1321 { 1322 Sec = &XLevel.Sectors[SecNum]; 1323 if (Sec->CeilingData) 1324 { 1325 continue; 1326 } 1327 // Add new door thinker 1328 RetCode = true; 1329 Door = Spawn(VerticalDoor); 1330 Door.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type); 1331 } 1332 } 1333 return RetCode; 1334} 1335 1336//========================================================================== 1337// 1338// EV_GenericDoor 1339// 1340// Boom's generic doors. 1341// 1342//========================================================================== 1343 1344final int EV_GenericDoor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1345 line_t* Line, Entity Thing) 1346{ 1347 int Tag; 1348 int LightTag; 1349 1350 // Check for locked door. 1351 if (Arg5 && !CheckLock(Thing, Arg5, true)) 1352 { 1353 return false; 1354 } 1355 1356 // Check for Boom's local door light special. 1357 if (Arg3 & 128) 1358 { 1359 Tag = 0; 1360 LightTag = Arg1; 1361 } 1362 else 1363 { 1364 Tag = Arg1; 1365 LightTag = 0; 1366 } 1367 1368 switch (Arg3 & 127) 1369 { 1370 case 0: 1371 return EV_DoDoor(Tag, Arg2, Arg4, LightTag, 0, 1372 VerticalDoor::DOOREV_Raise, Line, Thing); 1373 case 1: 1374 return EV_DoDoor(Tag, Arg2, LightTag, 0, 0, 1375 VerticalDoor::DOOREV_Open, Line, Thing); 1376 case 2: 1377 return EV_DoDoor(Tag, Arg2, Arg4, LightTag, 0, 1378 VerticalDoor::DOOREV_CloseWaitOpen, Line, Thing); 1379 case 3: 1380 return EV_DoDoor(Tag, Arg2, LightTag, 0, 0, 1381 VerticalDoor::DOOREV_Close, Line, Thing); 1382 } 1383 return false; 1384} 1385 1386//========================================================================== 1387// 1388// SpawnDoorCloseIn30 1389// 1390// Spawn a door that closes after 30 seconds 1391// 1392//========================================================================== 1393 1394final void SpawnDoorCloseIn30(sector_t* sec) 1395{ 1396 VerticalDoor Door; 1397 1398 Door = Spawn(VerticalDoor); 1399 Door.InitCloseIn30(sec); 1400 sec->special = 0; 1401} 1402 1403//========================================================================== 1404// 1405// SpawnDoorRaiseIn5Mins 1406// 1407// Spawn a door that opens after 5 minutes 1408// 1409//========================================================================== 1410 1411final void SpawnDoorRaiseIn5Mins(sector_t* sec) 1412{ 1413 VerticalDoor Door; 1414 1415 sec->special = 0; 1416 Door = Spawn(VerticalDoor); 1417 Door.Init(sec, 0, 16, 150, 0, 0, VerticalDoor::DOOREV_RaiseIn5Mins); 1418} 1419 1420//========================================================================== 1421// 1422// EV_TextureChangeDoor 1423// 1424//========================================================================== 1425 1426final int EV_TextureChangeDoor(int Arg1, int Arg2, int Arg3, int Arg4, 1427 int Arg5, line_t* Line, Entity E) 1428{ 1429 int i; 1430 int SecNum; 1431 int Rtn; 1432 sector_t* Sec; 1433 TextureChangeDoor Door; 1434 1435 Rtn = false; 1436 1437 if (!Arg1) 1438 { 1439 if (!Line || !Line->backsector) 1440 { 1441 return false; 1442 } 1443 1444 // if the sector has an active thinker, use it 1445 if (Line->backsector->CeilingData) 1446 { 1447 if (!E.bIsPlayer) 1448 return false; 1449 Door = TextureChangeDoor(Line->backsector->CeilingData); 1450 if (Door && Door.Direction == 0) 1451 { 1452 return Door.StartClosing(); 1453 } 1454 return false; 1455 } 1456 1457 // new door thinker 1458 if (FindAnimDoor(XLevel.Sides[Line->sidenum[0]].TopTexture)) 1459 { 1460 Door = Spawn(TextureChangeDoor); 1461 Door.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, Line); 1462 Rtn = true; 1463 } 1464 } 1465 else 1466 { 1467 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 1468 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 1469 { 1470 Sec = &XLevel.Sectors[SecNum]; 1471 if (Sec->CeilingData) 1472 { 1473 continue; 1474 } 1475 for (i = 0; i < Sec->linecount; i++) 1476 { 1477 Line = Sec->lines[i]; 1478 if (!Line->backsector) 1479 { 1480 continue; 1481 } 1482 // New door thinker 1483 if (FindAnimDoor(XLevel.Sides[Line->sidenum[0]].TopTexture)) 1484 { 1485 Rtn = true; 1486 Door = Spawn(TextureChangeDoor); 1487 Door.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Line); 1488 break; 1489 } 1490 } 1491 } 1492 } 1493 return Rtn; 1494} 1495 1496//************************************************************************** 1497// 1498// Ceilings 1499// 1500//************************************************************************** 1501 1502//========================================================================== 1503// 1504// EV_DoCeiling 1505// 1506// Move a ceiling up/down and all around! 1507// 1508//========================================================================== 1509 1510final int EV_DoCeiling(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1511 int Type, line_t* Line) 1512{ 1513 int SecNum; 1514 int Rtn; 1515 sector_t* Sec; 1516 CeilingMover Ceiling; 1517 1518 Rtn = false; 1519 1520 if (!Arg1) 1521 { 1522 if (!Line || !Line->backsector) 1523 return false; 1524 1525 // Reactivate in-stasis ceilings...for certain types. 1526 if ((Type == CeilingMover::CEILEV_CrushAndRaiseA || 1527 Type == CeilingMover::CEILEV_CrushAndRaiseSilA) && 1528 CeilingMover(Line->backsector->CeilingData)) 1529 { 1530 CeilingMover(Line->backsector->CeilingData).ActivateInStasis(0); 1531 } 1532 1533 if (!Line->backsector->CeilingData) 1534 { 1535 // new ceiling thinker 1536 Rtn = true; 1537 Ceiling = Spawn(CeilingMover); 1538 Ceiling.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, 1539 Type, Line); 1540 } 1541 } 1542 else 1543 { 1544 // Reactivate in-stasis ceilings...for certain types. 1545 if (Type == CeilingMover::CEILEV_CrushAndRaiseA || 1546 Type == CeilingMover::CEILEV_CrushAndRaiseSilA) 1547 { 1548 foreach AllThinkers(CeilingMover, Ceiling) 1549 { 1550 Ceiling.ActivateInStasis(Arg1); 1551 } 1552 } 1553 1554 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 1555 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 1556 { 1557 Sec = &XLevel.Sectors[SecNum]; 1558 if (Sec->CeilingData) 1559 continue; 1560 1561 // new ceiling thinker 1562 Rtn = true; 1563 Ceiling = Spawn(CeilingMover); 1564 Ceiling.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type, Line); 1565 } 1566 } 1567 return Rtn; 1568} 1569 1570//========================================================================== 1571// 1572// EV_CeilingCrushStop 1573// 1574// Stop a ceiling from crushing! 1575// 1576//========================================================================== 1577 1578final int EV_CeilingCrushStop(line_t* line, int Arg1, int Arg2, int Arg3, 1579 int Arg4, int Arg5) 1580{ 1581 int rtn; 1582 CeilingMover Ceiling; 1583 1584 rtn = false; 1585 foreach AllThinkers(CeilingMover, Ceiling) 1586 { 1587 if (Ceiling.CrushStop(Arg1)) 1588 { 1589 rtn = true; 1590 } 1591 } 1592 return rtn; 1593} 1594 1595//========================================================================== 1596// 1597// EV_StartCeilingWaggle 1598// 1599//========================================================================== 1600 1601final bool EV_StartCeilingWaggle(int Arg1, int Arg2, int Arg3, int Arg4, 1602 int Arg5) 1603{ 1604 int SectorIndex; 1605 sector_t* Sector; 1606 CeilingWaggle Waggle; 1607 bool RetCode; 1608 1609 RetCode = false; 1610 for (SectorIndex = XLevel.FindSectorFromTag(Arg1, -1); SectorIndex >= 0; 1611 SectorIndex = XLevel.FindSectorFromTag(Arg1, SectorIndex)) 1612 { 1613 Sector = &XLevel.Sectors[SectorIndex]; 1614 if (Sector->CeilingData) 1615 { 1616 // Already busy with another thinker 1617 continue; 1618 } 1619 RetCode = true; 1620 Waggle = Spawn(CeilingWaggle); 1621 Waggle.Init(Sector, Arg1, Arg2, Arg3, Arg4, Arg5); 1622 } 1623 return RetCode; 1624} 1625 1626//************************************************************************** 1627// 1628// Floors 1629// 1630//************************************************************************** 1631 1632//========================================================================== 1633// 1634// EV_DoFloor 1635// 1636// HANDLE FLOOR TYPES 1637// 1638//========================================================================== 1639 1640final int EV_DoFloor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1641 int Type, line_t* Line) 1642{ 1643 int SecNum; 1644 int Rtn; 1645 sector_t* Sec; 1646 FloorMover Floor; 1647 1648 Rtn = false; 1649 if (!Arg1) 1650 { 1651 if (!Line || !Line->backsector) 1652 return false; 1653 1654 if (!Line->backsector->FloorData) 1655 { 1656 // new floor thinker 1657 Rtn = true; 1658 Floor = Spawn(FloorMover); 1659 Floor.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, Type, 1660 Line); 1661 } 1662 } 1663 else 1664 { 1665 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 1666 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 1667 { 1668 Sec = &XLevel.Sectors[SecNum]; 1669 1670 // ALREADY MOVING? IF SO, KEEP GOING... 1671 if (Sec->FloorData) 1672 continue; 1673 1674 // new floor thinker 1675 Rtn = true; 1676 Floor = Spawn(FloorMover); 1677 Floor.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type, Line); 1678 } 1679 } 1680 return Rtn; 1681} 1682 1683//========================================================================== 1684// 1685// EV_FloorCrushStop 1686// 1687//========================================================================== 1688 1689final int EV_FloorCrushStop(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 1690{ 1691 FloorMover Floor; 1692 bool Rtn; 1693 1694 Rtn = false; 1695 foreach AllThinkers(FloorMover, Floor) 1696 { 1697 if (Floor.CrushStop(Arg1)) 1698 { 1699 Rtn = true; 1700 } 1701 } 1702 return Rtn; 1703} 1704 1705//========================================================================== 1706// 1707// EV_DoDonut() 1708// 1709// Handle donut function: lower pillar, raise surrounding pool, both to 1710// height, texture and type of the sector surrounding the pool. 1711// Passed the linedef that triggered the donut 1712// Returns whether a thinker was created 1713// 1714//========================================================================== 1715 1716final int EV_DoDonut(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 1717{ 1718 sector_t* s1; 1719 sector_t* s2; 1720 sector_t* s3; 1721 int secnum; 1722 int rtn; 1723 int i; 1724 FloorMover Floor; 1725 1726 rtn = 0; 1727 // do function on all sectors with same tag as linedef 1728 for (secnum = XLevel.FindSectorFromTag(Arg1, -1); secnum >= 0; 1729 secnum = XLevel.FindSectorFromTag(Arg1, secnum)) 1730 { 1731 s1 = &XLevel.Sectors[secnum]; // s1 is pillar's sector 1732 1733 // ALREADY MOVING? IF SO, KEEP GOING... 1734 if (s1->FloorData) 1735 continue; 1736 1737 s2 = getNextSector(s1->lines[0], s1); // s2 is pool's sector 1738 rtn = 1; 1739 1740 // find a two sided line around the pool whose other side isn't the pillar 1741 for (i = 0; i < s2->linecount; i++) 1742 { 1743 if ((!s2->lines[i]->flags & ML_TWOSIDED) || 1744 (s2->lines[i]->backsector == s1)) 1745 continue; 1746 s3 = s2->lines[i]->backsector; 1747 1748 // Spawn rising slime 1749 Floor = Spawn(FloorMover); 1750 Floor.InitDonut(s2, s3, Arg2); 1751 1752 // Spawn lowering donut-hole 1753 Floor = Spawn(FloorMover); 1754 Floor.InitDonut2(s1, s3, Arg3); 1755 break; 1756 } 1757 } 1758 return rtn; 1759} 1760 1761//========================================================================== 1762// 1763// EV_StartFloorWaggle 1764// 1765//========================================================================== 1766 1767final bool EV_StartFloorWaggle(int Arg1, int Arg2, int Arg3, int Arg4, 1768 int Arg5) 1769{ 1770 int SectorIndex; 1771 sector_t* Sector; 1772 FloorWaggle Waggle; 1773 bool RetCode; 1774 1775 RetCode = false; 1776 for (SectorIndex = XLevel.FindSectorFromTag(Arg1, -1); SectorIndex >= 0; 1777 SectorIndex = XLevel.FindSectorFromTag(Arg1, SectorIndex)) 1778 { 1779 Sector = &XLevel.Sectors[SectorIndex]; 1780 if (Sector->FloorData) 1781 { 1782 // Already busy with another thinker 1783 continue; 1784 } 1785 RetCode = true; 1786 Waggle = Spawn(FloorWaggle); 1787 Waggle.Init(Sector, Arg1, Arg2, Arg3, Arg4, Arg5); 1788 } 1789 return RetCode; 1790} 1791 1792//************************************************************************** 1793// 1794// Stairs 1795// 1796//************************************************************************** 1797 1798//========================================================================== 1799// 1800// EV_BuildStairsOld 1801// 1802// Build a staircase! 1803// 1804//========================================================================== 1805 1806final int EV_BuildStairsOld(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1807 bool Generic, line_t* Line) 1808{ 1809 int SecNum; 1810 float Height; 1811 int i; 1812 int j; 1813 int Ok; 1814 int Texture; 1815 int Rtn; 1816 sector_t* Sec; 1817 sector_t* TSec; 1818 FloorMover Floor; 1819 line_t* SecLine; 1820 int Direction; 1821 float StairSize; 1822 bool IgnTxt; 1823 int OldSecNum; 1824 1825 if (!Arg1 && (!Line || !Line->backsector)) 1826 { 1827 return false; 1828 } 1829 1830 if (Generic) 1831 { 1832 Direction = Arg4 & 1 ? 1 : -1; 1833 IgnTxt = !!(Arg4 & 2); 1834 } 1835 else 1836 { 1837 Direction = 1; 1838 IgnTxt = false; 1839 } 1840 StairSize = itof(Arg3 * Direction); 1841 Rtn = 0; 1842 for (SecNum = Arg1 ? XLevel.FindSectorFromTag(Arg1, -1) : 1; SecNum >= 0; 1843 SecNum = Arg1 ? XLevel.FindSectorFromTag(Arg1, SecNum) : -1) 1844 { 1845 Sec = Arg1 ? &XLevel.Sectors[SecNum] : Line->backsector; 1846 1847 // ALREADY MOVING? IF SO, KEEP GOING... 1848 if (Sec->FloorData) 1849 { 1850 continue; 1851 } 1852 1853 // new floor thinker 1854 Rtn = 1; 1855 Height = GetPlanePointZ(&Sec->floor, vector(0.0, 0.0, 0.0)) + 1856 StairSize; 1857 Floor = Spawn(FloorMover); 1858 Floor.InitStair(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Generic, Height); 1859 1860 Texture = Sec->floor.pic; 1861 OldSecNum = SecNum; //jff 3/4/98 preserve loop index 1862 1863 // Find next sector to raise 1864 // 1. Find 2-sided line with same sector side[0] 1865 // 2. Other side is the next sector to raise 1866 // 3. Unless already moving, or different texture, then stop building 1867 do 1868 { 1869 Ok = false; 1870 for (i = 0; i < Sec->linecount; i++) 1871 { 1872 SecLine = Sec->lines[i]; 1873 1874 if (!(SecLine->flags & ML_TWOSIDED)) 1875 continue; 1876 1877 TSec = SecLine->frontsector; 1878 if (Sec != TSec) 1879 continue; 1880 TSec = SecLine->backsector; 1881 if (!TSec) 1882 continue; //jff 5/7/98 if no backside, continue 1883 if (!IgnTxt && TSec->floor.pic != Texture) 1884 continue; 1885 1886 Height += StairSize; 1887 if (TSec->FloorData) 1888 continue; 1889 1890 Sec = TSec; 1891 // SecNum = TSec - XLevel.Sectors; 1892 for (j = 0; j < XLevel.NumSectors; j++) 1893 { 1894 if (TSec == &XLevel.Sectors[j]) 1895 { 1896 SecNum = j; 1897 break; 1898 } 1899 } 1900 Floor = Spawn(FloorMover); 1901 Floor.InitStair(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Generic, 1902 Height); 1903 Ok = true; 1904 break; 1905 } 1906 } 1907 while (Ok); 1908 if (!Level.CompatStairs) 1909 { 1910 SecNum = OldSecNum; //jff 3/4/98 restore loop index 1911 } 1912 } 1913 return Rtn; 1914} 1915 1916//========================================================================== 1917// 1918// EV_BuildStairs 1919// 1920// Build a staircase! 1921// 1922// StairDirection is either positive or negative, denoting build stairs 1923// up or down. 1924// 1925//========================================================================== 1926 1927final int EV_BuildStairs(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1928 int StairsType) 1929{ 1930 int SecNum; 1931 sector_t* Sec; 1932 StairStepMover StairStep; 1933 StairStepMover StairQueueHead; 1934 1935 StairQueueHead = none; 1936 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 1937 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 1938 { 1939 Sec = &XLevel.Sectors[SecNum]; 1940 1941 // ALREADY MOVING? IF SO, KEEP GOING... 1942 if (Sec->FloorData) 1943 { 1944 continue; 1945 } 1946 1947 StairStep = Spawn(StairStepMover); 1948 StairStep.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, 1949 StairsType); 1950 if (StairQueueHead) 1951 { 1952 StairQueueHead.AppendToQueue(StairStep); 1953 } 1954 else 1955 { 1956 StairQueueHead = StairStep; 1957 } 1958 Sec->special &= ~SECSPEC_BASE_MASK; 1959 } 1960 for (StairStep = StairQueueHead; StairStep; 1961 StairStep = StairStep.QueueNext) 1962 { 1963 StairStep.ProcessStairSector(); 1964 } 1965 return 1; 1966} 1967 1968//************************************************************************** 1969// 1970// Platforms 1971// 1972//************************************************************************** 1973 1974//========================================================================== 1975// 1976// EV_DoPlat 1977// 1978// Do Platforms. 1979// 1980//========================================================================== 1981 1982final int EV_DoPlat(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 1983 int Type, line_t* Line) 1984{ 1985 Platform Plat; 1986 int SecNum; 1987 int Rtn; 1988 sector_t* Sec; 1989 1990 Rtn = false; 1991 1992 if (!Arg1) 1993 { 1994 if (!Line || !Line->backsector) 1995 return false; 1996 1997 // Activate all <type> plats that are in stasis. 1998 if ((Type == Platform::PLATEV_PerpetualRaise || 1999 Type == Platform::PLATEV_PerpetualRaiseLip || 2000 Type == Platform::PLATEV_Toggle) && Platform(Line->backsector->FloorData)) 2001 { 2002 // Activate in stasis 2003 Platform(Line->backsector->FloorData).ActivateInStasis(Arg1); 2004 if (Type == Platform::PLATEV_Toggle) 2005 { 2006 Rtn = true; 2007 } 2008 } 2009 2010 if (!Line->backsector->FloorData) 2011 { 2012 // Find lowest & highest floors around sector 2013 Rtn = 1; 2014 Plat = Spawn(Platform); 2015 Plat.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, Type); 2016 } 2017 } 2018 else 2019 { 2020 // Activate all <type> plats that are in stasis. 2021 if (Type == Platform::PLATEV_PerpetualRaise || 2022 Type == Platform::PLATEV_PerpetualRaiseLip || 2023 Type == Platform::PLATEV_Toggle) 2024 { 2025 // Activate in stasis 2026 foreach AllThinkers(Platform, Plat) 2027 { 2028 Plat.ActivateInStasis(Arg1); 2029 } 2030 if (Type == Platform::PLATEV_Toggle) 2031 { 2032 Rtn = true; 2033 } 2034 } 2035 2036 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2037 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2038 { 2039 Sec = &XLevel.Sectors[SecNum]; 2040 if (Sec->FloorData) 2041 continue; 2042 2043 // Find lowest & highest floors around sector 2044 Rtn = 1; 2045 Plat = Spawn(Platform); 2046 Plat.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type); 2047 } 2048 } 2049 return Rtn; 2050} 2051 2052//========================================================================== 2053// 2054// EV_StopPlat 2055// 2056//========================================================================== 2057 2058final int EV_StopPlat(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 2059{ 2060 Platform Plat; 2061 2062 foreach AllThinkers(Platform, Plat) 2063 { 2064 Plat.StopPlat(Arg1); 2065 } 2066 return 1; 2067} 2068 2069//************************************************************************** 2070// 2071// Pillar 2072// 2073//************************************************************************** 2074 2075//========================================================================== 2076// 2077// EV_BuildPillar 2078// 2079//========================================================================== 2080 2081final int EV_BuildPillar(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 2082 bool Crush) 2083{ 2084 int SecNum; 2085 sector_t* Sec; 2086 Pillar pillar; 2087 int Rtn; 2088 2089 Rtn = false; 2090 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2091 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2092 { 2093 Sec = &XLevel.Sectors[SecNum]; 2094 if (Sec->FloorData || Sec->CeilingData) 2095 continue; // already moving 2096 Rtn = true; 2097 pillar = Spawn(Pillar); 2098 pillar.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Crush); 2099 } 2100 return Rtn; 2101} 2102 2103//========================================================================== 2104// 2105// EV_OpenPillar 2106// 2107//========================================================================== 2108 2109final int EV_OpenPillar(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 2110{ 2111 int SecNum; 2112 sector_t* Sec; 2113 Pillar pillar; 2114 int Rtn; 2115 2116 Rtn = false; 2117 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2118 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2119 { 2120 Sec = &XLevel.Sectors[SecNum]; 2121 if (Sec->FloorData || Sec->CeilingData) 2122 continue; // already moving 2123 Rtn = true; 2124 pillar = Spawn(Pillar); 2125 pillar.InitOpen(Sec, Arg1, Arg2, Arg3, Arg4, Arg5); 2126 } 2127 return Rtn; 2128} 2129 2130//************************************************************************** 2131// 2132// Elevator 2133// 2134//************************************************************************** 2135 2136//========================================================================== 2137// 2138// EV_DoElevator 2139// 2140//========================================================================== 2141 2142final int EV_DoElevator(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 2143 int Type, line_t* Line) 2144{ 2145 int SecNum; 2146 int Rtn; 2147 sector_t* Sec; 2148 Elevator Elev; 2149 2150 if (!Line && (Type == Elevator::ELEVEV_Current)) 2151 { 2152 return false; 2153 } 2154 2155 Rtn = false; 2156 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2157 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2158 { 2159 Sec = &XLevel.Sectors[SecNum]; 2160 2161 // Skip if already busy. 2162 if (Sec->FloorData || Sec->CeilingData) 2163 continue; 2164 2165 // New elevator thinker 2166 Rtn = true; 2167 Elev = Spawn(Elevator); 2168 Elev.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type, Line); 2169 } 2170 return Rtn; 2171} 2172 2173//************************************************************************** 2174// 2175// Polyobj Event Code 2176// 2177//************************************************************************** 2178 2179//========================================================================== 2180// 2181// EV_RotatePoly 2182// 2183//========================================================================== 2184 2185final bool EV_RotatePoly(line_t* line, int Arg1, int Arg2, int Arg3, 2186 int Arg4, int Arg5, int direction, bool overRide) 2187{ 2188 int mirror; 2189 int polyNum; 2190 PolyobjRotator pe; 2191 polyobj_t *poly; 2192 2193 polyNum = Arg1; 2194 poly = XLevel.GetPolyobj(polyNum); 2195 if (poly) 2196 { 2197 if (poly->SpecialData && !overRide) 2198 { // poly is already moving 2199 return false; 2200 } 2201 } 2202 else 2203 { 2204 Error("EV_RotatePoly: Invalid polyobj num"); //: %d\n", polyNum); 2205 } 2206 pe = Spawn(PolyobjRotator); 2207 pe.polyobj = polyNum; 2208 if (Arg3) 2209 { 2210 if (Arg3 == 255) 2211 { 2212 pe.dist = -1.0; 2213 } 2214 else 2215 { 2216 pe.dist = itof(Arg3) * (90.0 / 64.0); // Angle 2217 } 2218 } 2219 else 2220 { 2221 pe.dist = 360.0; 2222 } 2223 pe.speed = AngleMod180(32.0 * itof(Arg2) * itof(direction) * 90.0 / 64.0 / 8.0); 2224 2225 //THRUST 2226 pe.thrust_force = pe.speed / 32.0 * itof(0x800) / 90.0; 2227 2228 poly->SpecialData = pe; 2229 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2230 2231 for (mirror = XLevel.GetPolyobjMirror(polyNum); mirror; 2232 mirror = XLevel.GetPolyobjMirror(polyNum)) 2233 { 2234 poly = XLevel.GetPolyobj(mirror); 2235 if (!poly) 2236 { 2237 Error("EV_RotatePoly: Invalid polyobj num"); //: %d\n", polyNum); 2238 } 2239 if (poly && poly->SpecialData && !overRide) 2240 { // mirroring poly is already in motion 2241 break; 2242 } 2243 pe = Spawn(PolyobjRotator); 2244 poly->SpecialData = pe; 2245 pe.polyobj = mirror; 2246 if (Arg3) 2247 { 2248 if (Arg3 == 255) 2249 { 2250 pe.dist = -1.0; 2251 } 2252 else 2253 { 2254 pe.dist = itof(Arg3) * (90.0 / 64.0); // Angle 2255 } 2256 } 2257 else 2258 { 2259 pe.dist = 360.0; 2260 } 2261 direction = -direction; 2262 pe.speed = AngleMod180(32.0 * itof(Arg2) * itof(direction) * 90.0 / 64.0 / 8.0); 2263 2264 //THRUST 2265 pe.thrust_force = pe.speed / 32.0 * itof(0x800) / 90.0; 2266 2267 polyNum = mirror; 2268 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2269 } 2270 return true; 2271} 2272 2273//========================================================================== 2274// 2275// EV_MovePoly 2276// 2277//========================================================================== 2278 2279final bool EV_MovePoly(line_t * line, int Arg1, int Arg2, int Arg3, int Arg4, 2280 int Arg5, bool timesEight, bool overRide) 2281{ 2282 int mirror; 2283 int polyNum; 2284 PolyobjMover pe; 2285 polyobj_t *poly; 2286 float an; 2287 2288 polyNum = Arg1; 2289 poly = XLevel.GetPolyobj(polyNum); 2290 if (poly) 2291 { 2292 if (poly->SpecialData && !overRide) 2293 { // poly is already moving 2294 return false; 2295 } 2296 } 2297 else 2298 { 2299 Error("EV_MovePoly: Invalid polyobj num"); //: %d\n", polyNum); 2300 } 2301 pe = Spawn(PolyobjMover); 2302 pe.polyobj = polyNum; 2303 if (timesEight) 2304 { 2305 pe.dist = itof(Arg4) * 8.0; 2306 } 2307 else 2308 { 2309 pe.dist = itof(Arg4); // Distance 2310 } 2311 pe.speed = itof(Arg2) * 4.0; 2312 2313 //THRUST 2314 pe.thrust_force = pe.speed / 8.0; 2315 2316 poly->SpecialData = pe; 2317 2318 an = itof(Arg3) * (90.0 / 64.0); 2319 2320 pe.angle = an; 2321 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2322 2323 for (mirror = XLevel.GetPolyobjMirror(polyNum); mirror; 2324 mirror = XLevel.GetPolyobjMirror(polyNum)) 2325 { 2326 poly = XLevel.GetPolyobj(mirror); 2327 if (poly && poly->SpecialData && !overRide) 2328 { // mirroring poly is already in motion 2329 break; 2330 } 2331 pe = Spawn(PolyobjMover); 2332 pe.polyobj = mirror; 2333 poly->SpecialData = pe; 2334 if (timesEight) 2335 { 2336 pe.dist = itof(Arg4) * 8.0; 2337 } 2338 else 2339 { 2340 pe.dist = itof(Arg4); // Distance 2341 } 2342 pe.speed = itof(Arg2) * 4.0; 2343 2344 //THRUST 2345 pe.thrust_force = pe.speed / 8.0; 2346 2347 an = AngleMod360(an + 180.0); // reverse the angle 2348 pe.angle = an; 2349 polyNum = mirror; 2350 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2351 } 2352 return true; 2353} 2354 2355//========================================================================== 2356// 2357// EV_OpenPolyDoor 2358// 2359//========================================================================== 2360 2361final bool EV_OpenPolyDoor(line_t * line, int Arg1, int Arg2, int Arg3, 2362 int Arg4, int Arg5, int type) 2363{ 2364 int mirror; 2365 int polyNum; 2366 PolyobjDoor pd; 2367 polyobj_t *poly; 2368 float an = 0.0; 2369 2370 polyNum = Arg1; 2371 poly = XLevel.GetPolyobj(polyNum); 2372 if (poly) 2373 { 2374 if (poly->SpecialData) 2375 { // poly is already moving 2376 return false; 2377 } 2378 } 2379 else 2380 { 2381 Error("EV_OpenPolyDoor: Invalid polyobj num"); //: %d\n", polyNum); 2382 } 2383 pd = Spawn(PolyobjDoor); 2384 pd.type = type; 2385 pd.polyobj = polyNum; 2386 if (type == PolyobjDoor::PODOOR_SLIDE) 2387 { 2388 pd.waitTime = itof(Arg5) / 35.0; 2389 pd.speed = itof(Arg2) * 4.0; 2390 pd.totalDist = itof(Arg4); // Distance 2391 pd.dist = pd.totalDist; 2392 an = itof(Arg3) * (90.0 / 64.0); 2393 pd.xSpeed = cos(an); 2394 pd.ySpeed = sin(an); 2395 2396 //THRUST 2397 pd.thrust_force = pd.speed / 8.0; 2398 2399 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2400 } 2401 else if (type == PolyobjDoor::PODOOR_SWING) 2402 { 2403 pd.waitTime = itof(Arg4) / 35.0; 2404 pd.speed = AngleMod180(4.0 * itof(Arg2) * (90.0 / 64.0)); 2405 pd.totalDist = itof(Arg3) * (90.0 / 64.0); 2406 pd.dist = pd.totalDist; 2407 2408 //THRUST 2409 pd.thrust_force = pd.speed * itof(0x1000) / 180.0; 2410 2411 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2412 } 2413 2414 poly->SpecialData = pd; 2415 2416 for (mirror = XLevel.GetPolyobjMirror(polyNum); mirror; 2417 mirror = XLevel.GetPolyobjMirror(polyNum)) 2418 { 2419 poly = XLevel.GetPolyobj(mirror); 2420 if (poly && poly->SpecialData) 2421 { // mirroring poly is already in motion 2422 break; 2423 } 2424 pd = Spawn(PolyobjDoor); 2425 pd.polyobj = mirror; 2426 pd.type = type; 2427 poly->SpecialData = pd; 2428 if (type == PolyobjDoor::PODOOR_SLIDE) 2429 { 2430 pd.waitTime = itof(Arg5) / 35.0; 2431 pd.speed = itof(Arg2) * 4.0; 2432 pd.totalDist = itof(Arg4); // Distance 2433 pd.dist = pd.totalDist; 2434 an = AngleMod360(an + 180.0); // reverse the angle 2435 pd.xSpeed = cos(an); 2436 pd.ySpeed = sin(an); 2437 2438 //THRUST 2439 pd.thrust_force = pd.speed / 8.0; 2440 2441 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2442 } 2443 else if (type == PolyobjDoor::PODOOR_SWING) 2444 { 2445 pd.waitTime = itof(Arg4) / 35.0; 2446 pd.speed = AngleMod180(4.0 * itof(-Arg2) * (90.0 / 64.0)); 2447 pd.totalDist = itof(Arg3) * (90.0 / 64.0); 2448 pd.dist = pd.totalDist; 2449 2450 //THRUST 2451 pd.thrust_force = pd.speed * itof(0x1000) / 180.0; 2452 2453 PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0); 2454 } 2455 polyNum = mirror; 2456 } 2457 return true; 2458} 2459 2460//************************************************************************** 2461// 2462// Light specials 2463// 2464//************************************************************************** 2465 2466//========================================================================== 2467// 2468// SpawnFireFlicker 2469// 2470//========================================================================== 2471 2472final void SpawnFireFlicker(sector_t* sector) 2473{ 2474 FireFlicker Flick; 2475 2476 Flick = Spawn(FireFlicker); 2477 Flick.Init(sector); 2478} 2479 2480//========================================================================== 2481// 2482// SpawnGlowingLight 2483// 2484// Spawn glowing light 2485// 2486//========================================================================== 2487 2488final void SpawnGlowingLight(sector_t* sector) 2489{ 2490 GlowingLight G; 2491 2492 G = Spawn(GlowingLight); 2493 G.Init(sector); 2494} 2495 2496//========================================================================== 2497// 2498// SpawnLightFlash 2499// 2500//========================================================================== 2501 2502final void SpawnLightFlash(sector_t* sector) 2503{ 2504 LightFlash Flash; 2505 2506 Flash = Spawn(LightFlash); 2507 Flash.Init(sector); 2508} 2509 2510//========================================================================== 2511// 2512// SpawnStrobeFlash 2513// 2514//========================================================================== 2515 2516final void SpawnStrobeFlash(sector_t* sector, int fastOrSlow, int maxtime, 2517 int inSync) 2518{ 2519 Strobe Flash; 2520 2521 Flash = Spawn(Strobe); 2522 Flash.Init(sector, fastOrSlow, maxtime, inSync); 2523} 2524 2525//========================================================================== 2526// 2527// SpawnPhasedLight 2528// 2529//========================================================================== 2530 2531final void SpawnPhasedLight(sector_t * sector, int base, int index) 2532{ 2533 PhasedLight Phase; 2534 2535 Phase = Spawn(PhasedLight); 2536 Phase.Init(sector, base, index); 2537} 2538 2539//========================================================================== 2540// 2541// SpawnLightSequence 2542// 2543//========================================================================== 2544 2545final void SpawnLightSequence(sector_t * sector, float indexStep) 2546{ 2547 sector_t *sec; 2548 sector_t *nextSec; 2549 sector_t *tempSec; 2550 int seqSpecial; 2551 int i; 2552 float count; 2553 float index; 2554 float indexDelta; 2555 int base; 2556 2557 seqSpecial = SECSPEC_LightSequence; // look for Light_Sequence, first 2558 sec = sector; 2559 count = 1.0; 2560 do 2561 { 2562 nextSec = NULL; 2563 // Make sure that the search doesn't back up. 2564 sec->special = (sec->special & ~SECSPEC_BASE_MASK) | 2565 SECSPEC_LightSequenceStart; 2566 for (i = 0; i < sec->linecount; i++) 2567 { 2568 tempSec = getNextSector(sec->lines[i], sec); 2569 if (!tempSec) 2570 { 2571 continue; 2572 } 2573 if ((tempSec->special & SECSPEC_BASE_MASK) == seqSpecial) 2574 { 2575 if (seqSpecial == SECSPEC_LightSequence) 2576 { 2577 seqSpecial = SECSPEC_LightSequenceAlt; 2578 } 2579 else 2580 { 2581 seqSpecial = SECSPEC_LightSequence; 2582 } 2583 nextSec = tempSec; 2584 count += 1.0; 2585 } 2586 } 2587 sec = nextSec; 2588 } 2589 while (sec); 2590 2591 sec = sector; 2592 count *= indexStep; 2593 index = 0.0; 2594 indexDelta = 64.0 / count; 2595 base = sector->params.lightlevel; 2596 do 2597 { 2598 nextSec = NULL; 2599 if (sec->params.lightlevel) 2600 { 2601 base = sec->params.lightlevel; 2602 } 2603 SpawnPhasedLight(sec, base, ftoi(index)); 2604 // Clear sector special. 2605 sec->special &= ~SECSPEC_BASE_MASK; 2606 index += indexDelta; 2607 for (i = 0; i < sec->linecount; i++) 2608 { 2609 tempSec = getNextSector(sec->lines[i], sec); 2610 if (!tempSec) 2611 { 2612 continue; 2613 } 2614 if ((tempSec->special & SECSPEC_BASE_MASK) == 2615 SECSPEC_LightSequenceStart) 2616 { 2617 nextSec = tempSec; 2618 } 2619 } 2620 sec = nextSec; 2621 } 2622 while (sec); 2623} 2624 2625//========================================================================== 2626// 2627// EV_StartLightStrobing 2628// 2629// Start strobing lights (usually from a trigger) 2630// 2631//========================================================================== 2632 2633final int EV_StartLightStrobing(int Arg1, int Arg2, int Arg3, int Arg4, 2634 int Arg5) 2635{ 2636 int Ret; 2637 int SecNum; 2638 sector_t* Sec; 2639 Strobe Flash; 2640 2641 Ret = false; 2642 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2643 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2644 { 2645 Sec = &XLevel.Sectors[SecNum]; 2646 if (Sec->LightingData) 2647 continue; 2648 2649 Ret = true; 2650 Flash = Spawn(Strobe); 2651 Flash.Init(Sec, Arg3, Arg2, Arg4); 2652 } 2653 return Ret; 2654} 2655 2656//========================================================================== 2657// 2658// EV_TagLightTurnOn 2659// 2660// Turn line's tag lights on 2661// 2662//========================================================================== 2663 2664final int EV_TagLightTurnOn(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 2665{ 2666 int SecNum; 2667 sector_t* Sec; 2668 int j; 2669 sector_t* TSec; 2670 int Max; 2671 2672 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2673 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2674 { 2675 Sec = &XLevel.Sectors[SecNum]; 2676 Max = 0; 2677 for (j = 0; j < Sec->linecount; j++) 2678 { 2679 TSec = getNextSector(Sec->lines[j], Sec); 2680 if (!TSec) 2681 continue; 2682 if (TSec->params.lightlevel > Max) 2683 Max = TSec->params.lightlevel; 2684 } 2685 Sec->params.lightlevel = Max; 2686 } 2687 return 1; 2688} 2689 2690//========================================================================== 2691// 2692// EV_TurnTagLightsOff 2693// 2694// Turn line's tag lights off 2695// 2696//========================================================================== 2697 2698final int EV_TurnTagLightsOff(int Arg1, int Arg2, int Arg3, int Arg4, 2699 int Arg5) 2700{ 2701 int SecNum; 2702 sector_t* Sec; 2703 int i; 2704 int Min; 2705 sector_t* TSec; 2706 2707 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2708 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2709 { 2710 Sec = &XLevel.Sectors[SecNum]; 2711 Min = Sec->params.lightlevel; 2712 for (i = 0; i < Sec->linecount; i++) 2713 { 2714 TSec = getNextSector(Sec->lines[i], Sec); 2715 if (!TSec) 2716 continue; 2717 if (TSec->params.lightlevel < Min) 2718 Min = TSec->params.lightlevel; 2719 } 2720 Sec->params.lightlevel = Min; 2721 } 2722 return 1; 2723} 2724 2725//============================================================================ 2726// 2727// EV_LightRaiseByValue 2728// 2729//============================================================================ 2730 2731final bool EV_LightRaiseByValue(int Arg1, int Arg2, int Arg3, int Arg4, 2732 int Arg5) 2733{ 2734 sector_t* Sec; 2735 int SecNum; 2736 bool Rtn; 2737 2738 Rtn = false; 2739 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2740 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2741 { 2742 Sec = &XLevel.Sectors[SecNum]; 2743 Sec->params.lightlevel += Arg2; 2744 if (Sec->params.lightlevel > 255) 2745 { 2746 Sec->params.lightlevel = 255; 2747 } 2748 Rtn = true; 2749 } 2750 return Rtn; 2751} 2752 2753//============================================================================ 2754// 2755// EV_LightLowerByValue 2756// 2757//============================================================================ 2758 2759final bool EV_LightLowerByValue(int Arg1, int Arg2, int Arg3, int Arg4, 2760 int Arg5) 2761{ 2762 sector_t* Sec; 2763 int SecNum; 2764 bool Rtn; 2765 2766 Rtn = false; 2767 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2768 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2769 { 2770 Sec = &XLevel.Sectors[SecNum]; 2771 Sec->params.lightlevel -= Arg2; 2772 if (Sec->params.lightlevel < 0) 2773 { 2774 Sec->params.lightlevel = 0; 2775 } 2776 Rtn = true; 2777 } 2778 return Rtn; 2779} 2780 2781//============================================================================ 2782// 2783// EV_LightChangeToValue 2784// 2785//============================================================================ 2786 2787final bool EV_LightChangeToValue(int Arg1, int Arg2, int Arg3, int Arg4, 2788 int Arg5) 2789{ 2790 sector_t* Sec; 2791 int SecNum; 2792 bool Rtn; 2793 2794 Rtn = false; 2795 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2796 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2797 { 2798 Sec = &XLevel.Sectors[SecNum]; 2799 Sec->params.lightlevel = Arg2; 2800 if (Sec->params.lightlevel < 0) 2801 { 2802 Sec->params.lightlevel = 0; 2803 } 2804 else if (Sec->params.lightlevel > 255) 2805 { 2806 Sec->params.lightlevel = 255; 2807 } 2808 Rtn = true; 2809 } 2810 return Rtn; 2811} 2812 2813//============================================================================ 2814// 2815// EV_SpawnLight 2816// 2817//============================================================================ 2818 2819final bool EV_SpawnLight(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 2820 int Type) 2821{ 2822 LightEffect Light; 2823 sector_t* Sec; 2824 int SecNum; 2825 bool Rtn; 2826 2827 Rtn = false; 2828 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 2829 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 2830 { 2831 Sec = &XLevel.Sectors[SecNum]; 2832 Light = Spawn(LightEffect); 2833 Light.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type); 2834 Rtn = true; 2835 } 2836 return Rtn; 2837} 2838 2839//========================================================================== 2840// 2841// EV_LightStop 2842// 2843//========================================================================== 2844 2845final bool EV_LightStop(int Tag) 2846{ 2847 Lighting L; 2848 2849 foreach AllThinkers(Lighting, L) 2850 { 2851 if (L.Sector->tag == Tag) 2852 { 2853 if (L.Sector->LightingData == L) 2854 L.Sector->LightingData = none; 2855 L.Destroy(); 2856 } 2857 } 2858 return true; 2859} 2860 2861//========================================================================== 2862// 2863// SpawnTransferWallLight 2864// 2865//========================================================================== 2866 2867final void SpawnTransferWallLight(line_t* Line) 2868{ 2869 WallLightTransfer Tr; 2870 2871 Tr = Spawn(WallLightTransfer); 2872 Tr.Init(Line->frontsector, Line->arg1, Line->arg2, Line->arg3, 2873 Line->arg4, Line->arg5); 2874} 2875 2876//************************************************************************** 2877// 2878// Scrollers 2879// 2880//************************************************************************** 2881 2882//========================================================================== 2883// 2884// SpawnScrollingFloor 2885// 2886//========================================================================== 2887 2888final void SpawnScrollingFloor(sector_t* Sector, int XDir, int YDir, 2889 int Speed) 2890{ 2891 Scroller Scroll; 2892 2893 Scroll = Spawn(Scroller); 2894 Scroll.InitFloor(Sector, XDir, YDir, Speed); 2895} 2896 2897//========================================================================== 2898// 2899// SpawnWallScroller 2900// 2901//========================================================================== 2902 2903final void SpawnWallScroller(line_t* Line, int XDir, int YDir) 2904{ 2905 Scroller Scroll; 2906 2907 Scroll = Spawn(Scroller); 2908 Scroll.InitWall(Line, XDir, YDir); 2909} 2910 2911//========================================================================== 2912// 2913// SpawnWallOffsetsScroller 2914// 2915//========================================================================== 2916 2917final void SpawnWallOffsetsScroller(line_t* Line) 2918{ 2919 Scroller Scroll; 2920 2921 Scroll = Spawn(Scroller); 2922 Scroll.InitWallOffsets(Line); 2923} 2924 2925//========================================================================== 2926// 2927// SpawnTextureBothScroller 2928// 2929//========================================================================== 2930 2931final void SpawnTextureBothScroller(line_t* Line) 2932{ 2933 Scroller Scroll; 2934 2935 Scroll = Spawn(Scroller); 2936 Scroll.InitTextureBoth(Line, itof(Line->arg2 - Line->arg3) / 64.0, 2937 itof(Line->arg5 - Line->arg4) / 64.0, 0, 7); 2938} 2939 2940//========================================================================== 2941// 2942// SpawnScrollCeiling 2943// 2944//========================================================================== 2945 2946final void SpawnScrollCeiling(line_t* Line) 2947{ 2948 Scroller Scroll; 2949 int SecNum; 2950 2951 for (SecNum = XLevel.FindSectorFromTag(Line->arg1, -1); SecNum >= 0; 2952 SecNum = XLevel.FindSectorFromTag(Line->arg1, SecNum)) 2953 { 2954 Scroll = Spawn(Scroller); 2955 Scroll.InitGen(Scroller::SCROLLEV_Ceiling, Line, SecNum); 2956 } 2957} 2958 2959//========================================================================== 2960// 2961// SpawnScrollFloor 2962// 2963//========================================================================== 2964 2965final void SpawnScrollFloor(line_t* Line) 2966{ 2967 Scroller Scroll; 2968 int SecNum; 2969 2970 for (SecNum = XLevel.FindSectorFromTag(Line->arg1, -1); SecNum >= 0; 2971 SecNum = XLevel.FindSectorFromTag(Line->arg1, SecNum)) 2972 { 2973 if (Line->arg3 != 1) 2974 { 2975 // Scroll the floor texture 2976 Scroll = Spawn(Scroller); 2977 Scroll.InitGen(Scroller::SCROLLEV_Floor, Line, SecNum); 2978 } 2979 2980 if (Line->arg3 > 0) 2981 { 2982 // Carry objects on the floor 2983 Scroll = Spawn(Scroller); 2984 Scroll.InitGen(Scroller::SCROLLEV_Carry, Line, SecNum); 2985 } 2986 } 2987} 2988 2989//========================================================================== 2990// 2991// SpawnScrollTextureModel 2992// 2993// Scroll wall according to linedef 2994// (same direction and speed as scrolling floors) 2995// 2996//========================================================================== 2997 2998final void SpawnScrollTextureModel(line_t* Line) 2999{ 3000 Scroller Scroll; 3001 int Searcher; 3002 line_t* Other; 3003 3004 Searcher = -1; 3005 for (Other = XLevel.FindLine(Line->arg1, &Searcher); Other; 3006 Other = XLevel.FindLine(Line->arg1, &Searcher)) 3007 { 3008 if (Line != Other) 3009 { 3010 Scroll = Spawn(Scroller); 3011 Scroll.InitTextureModel(Other, Line); 3012 } 3013 } 3014} 3015 3016//========================================================================== 3017// 3018// EV_ScrollTextureBoth 3019// 3020//========================================================================== 3021 3022final bool EV_ScrollTextureBoth(int Arg1, int Arg2, int Arg3, int Arg4, 3023 int Arg5) 3024{ 3025 if (!Arg1) 3026 { 3027 // No lines to adjust. 3028 return false; 3029 } 3030 3031 int WhichSide = 0; 3032 if (Arg1 < 0) 3033 { 3034 WhichSide = 1; 3035 Arg1 = -Arg1; 3036 } 3037 3038 SetWallScroller(Arg1, itof(Arg2 - Arg3) / 64.0, itof(Arg5 - Arg4) / 64.0, 3039 WhichSide, 7); 3040 return true; 3041} 3042 3043//========================================================================== 3044// 3045// EV_ScrollWall 3046// 3047//========================================================================== 3048 3049final bool EV_ScrollWall(int Arg1, int Arg2, int Arg3, int Arg4, 3050 int Arg5) 3051{ 3052 if (!Arg1) 3053 { 3054 // No lines to adjust. 3055 return false; 3056 } 3057 3058 SetWallScroller(Arg1, itof(Arg2) / itof(0x10000), 3059 itof(Arg3) / itof(0x10000), !!Arg4, Arg5); 3060 return true; 3061} 3062 3063//========================================================================== 3064// 3065// SetWallScroller 3066// 3067//========================================================================== 3068 3069final void SetWallScroller(int LineId, float XSpeed, float YSpeed, 3070 int WhichSide, int Where) 3071{ 3072 Scroller Scroll; 3073 int i; 3074 3075 Where &= 7; 3076 if (!Where) 3077 { 3078 return; 3079 } 3080 3081 if (!XSpeed && !YSpeed) 3082 { 3083 // As a special case with no deltas remove any texture scrolers. 3084 foreach AllThinkers(Scroller, Scroll) 3085 { 3086 if (Scroll.Type != Scroller::SCROLLEV_Side) 3087 { 3088 continue; 3089 } 3090 // Check if line has a correct tag. 3091 if (Scroll.AffecteeSrcLine->LineTag != LineId) 3092 { 3093 continue; 3094 } 3095 // Check if it's the correct side. 3096 if (Scroll.AffecteeSrcLine->sidenum[WhichSide] != Scroll.Affectee) 3097 { 3098 continue; 3099 } 3100 // Check if it's scrolling the same wall parts 3101 if (Scroll.SideParts != Where) 3102 { 3103 continue; 3104 } 3105 // OK, destroy the thinker. 3106 Scroll.Destroy(); 3107 } 3108 } 3109 else 3110 { 3111 array<Scroller> FoundScrollers; 3112 3113 foreach AllThinkers(Scroller, Scroll) 3114 { 3115 if (Scroll.Type != Scroller::SCROLLEV_Side) 3116 { 3117 continue; 3118 } 3119 // Check if line has a correct tag. 3120 if (Scroll.AffecteeSrcLine->LineTag != LineId) 3121 { 3122 continue; 3123 } 3124 // Check if it's the correct side. 3125 if (Scroll.AffecteeSrcLine->sidenum[WhichSide] != Scroll.Affectee) 3126 { 3127 continue; 3128 } 3129 // Check if it's scrolling the same wall parts 3130 if (Scroll.SideParts != Where) 3131 { 3132 continue; 3133 } 3134 // Found it. 3135 Scroll.AdjustTextureBoth(XSpeed, YSpeed); 3136 FoundScrollers[FoundScrollers.Num] = Scroll; 3137 } 3138 3139 int Searcher; 3140 line_t* Other; 3141 3142 Searcher = -1; 3143 for (Other = XLevel.FindLine(LineId, &Searcher); Other; 3144 Other = XLevel.FindLine(LineId, &Searcher)) 3145 { 3146 // Check if this line already has scroller. 3147 for (i = 0; i < FoundScrollers.Num; i++) 3148 { 3149 if (FoundScrollers[i].Affectee == Other->sidenum[WhichSide]) 3150 { 3151 break; 3152 } 3153 } 3154 if (i == FoundScrollers.Num) 3155 { 3156 // Start a new scroller. 3157 Scroll = Spawn(Scroller); 3158 Scroll.InitTextureBoth(Other, XSpeed, YSpeed, WhichSide, 3159 Where); 3160 } 3161 } 3162 } 3163} 3164 3165//========================================================================== 3166// 3167// EV_ScrollFloor 3168// 3169//========================================================================== 3170 3171final bool EV_ScrollFloor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 3172{ 3173 // Set floor scrolling 3174 if (Arg4 == 0 || Arg4 == 2) 3175 { 3176 SetScroller(Scroller::SCROLLEV_Floor, Arg1, Arg2, Arg3); 3177 } 3178 else 3179 { 3180 SetScroller(Scroller::SCROLLEV_Floor, Arg1, 0, 0); 3181 } 3182 3183 // Set carrying of items. 3184 if (Arg4 > 0) 3185 { 3186 SetScroller(Scroller::SCROLLEV_Carry, Arg1, Arg2, Arg3); 3187 } 3188 else 3189 { 3190 SetScroller(Scroller::SCROLLEV_Carry, Arg1, 0, 0); 3191 } 3192 return true; 3193} 3194 3195//========================================================================== 3196// 3197// EV_ScrollCeiling 3198// 3199//========================================================================== 3200 3201final bool EV_ScrollCeiling(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 3202{ 3203 SetScroller(Scroller::SCROLLEV_Ceiling, Arg1, Arg2, Arg3); 3204 return true; 3205} 3206 3207//========================================================================== 3208// 3209// SetScroller 3210// 3211//========================================================================== 3212 3213final void SetScroller(int Type, int Arg1, int Arg2, int Arg3) 3214{ 3215 // Adjust existing scrollers. If there is any, it means that all 3216 // tagged sectors have them and there's no need to spawn new ones. 3217 Scroller Sc; 3218 bool Found = false; 3219 foreach AllThinkers(Scroller, Sc) 3220 { 3221 if (Sc.Type == Type && Sc.Sector->tag == Arg1) 3222 { 3223 Sc.SetSpeed(Arg2, Arg3); 3224 Found = true; 3225 } 3226 } 3227 3228 if (Found) 3229 { 3230 return; 3231 } 3232 // Don't spawn scrollers if both speeds are 0. 3233 if (!Arg2 && !Arg3) 3234 { 3235 return; 3236 } 3237 3238 int SecNum; 3239 3240 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3241 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3242 { 3243 Sc = Spawn(Scroller); 3244 Sc.InitScripted(Type, Arg2, Arg3, SecNum); 3245 } 3246} 3247 3248//************************************************************************** 3249// 3250// Transfering floor texture and sector special 3251// 3252//************************************************************************** 3253 3254//========================================================================== 3255// 3256// EV_FloorTransferTrigger 3257// 3258//========================================================================== 3259 3260final bool EV_FloorTransferTrigger(int Arg1, int Arg2, int Arg3, int Arg4, 3261 int Arg5, line_t* Line) 3262{ 3263 int SecNum; 3264 bool Rtn; 3265 sector_t* Sec; 3266 3267 if (!Line) 3268 { 3269 return false; 3270 } 3271 3272 Rtn = false; 3273 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3274 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3275 { 3276 Sec = &XLevel.Sectors[SecNum]; 3277 Rtn = true; 3278 3279 Sec->floor.pic = Line->frontsector->floor.pic; 3280 Sec->special = (Sec->special & SECSPEC_SECRET_MASK) | 3281 (Line->frontsector->special & ~SECSPEC_SECRET_MASK); 3282 } 3283 return Rtn; 3284} 3285 3286//========================================================================== 3287// 3288// EV_FloorTransferNumeric 3289// 3290//========================================================================== 3291 3292final bool EV_FloorTransferNumeric(int Arg1, int Arg2, int Arg3, int Arg4, 3293 int Arg5) 3294{ 3295 int SecNum; 3296 bool Rtn; 3297 sector_t* Sec; 3298 sector_t* MdlSec; 3299 3300 Rtn = false; 3301 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3302 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3303 { 3304 Sec = &XLevel.Sectors[SecNum]; 3305 Rtn = true; 3306 3307 MdlSec = FindModelFloorSector(Sec, GetPlanePointZ(&Sec->floor, 3308 Sec->soundorg)); 3309 if (MdlSec) 3310 { 3311 Sec->floor.pic = MdlSec->floor.pic; 3312 Sec->special = MdlSec->special; 3313 } 3314 } 3315 return Rtn; 3316} 3317 3318//************************************************************************ 3319// 3320// Changing of sector properties 3321// 3322//************************************************************************ 3323 3324//========================================================================= 3325// 3326// EV_SectorSoundChange 3327// 3328//========================================================================= 3329 3330final bool EV_SectorSoundChange(int Arg1, int Arg2, int Arg3, int Arg4, 3331 int Arg5) 3332{ 3333 int secNum; 3334 bool rtn; 3335 3336 if (!Arg1) 3337 { 3338 return false; 3339 } 3340 rtn = false; 3341 for (secNum = XLevel.FindSectorFromTag(Arg1, -1); secNum >= 0; 3342 secNum = XLevel.FindSectorFromTag(Arg1, secNum)) 3343 { 3344 XLevel.Sectors[secNum].seqType = Arg2; 3345 rtn = true; 3346 } 3347 return rtn; 3348} 3349 3350//========================================================================= 3351// 3352// EV_SectorSetColour 3353// 3354//========================================================================= 3355 3356final bool EV_SectorSetColour(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 3357{ 3358 int SecNum; 3359 int Col; 3360 3361 Col = RGBA(Arg2, Arg3, Arg4, 0); 3362 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3363 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3364 { 3365 XLevel.Sectors[SecNum].params.LightColour = Col; 3366 } 3367 return true; 3368} 3369 3370//========================================================================= 3371// 3372// EV_SectorSetFade 3373// 3374//========================================================================= 3375 3376final bool EV_SectorSetFade(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 3377{ 3378 int SecNum; 3379 int Fade; 3380 3381 Fade = RGBA(Arg2, Arg3, Arg4, 255); 3382 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3383 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3384 { 3385 XLevel.Sectors[SecNum].params.Fade = Fade; 3386 } 3387 return true; 3388} 3389 3390//========================================================================= 3391// 3392// EV_SectorSetDamage 3393// 3394//========================================================================= 3395 3396final bool EV_SectorSetDamage(int Arg1, int Arg2, int Arg3, int Arg4, 3397 int Arg5) 3398{ 3399 int SecNum; 3400 bool Rtn; 3401 3402 if (!Arg1) 3403 { 3404 return false; 3405 } 3406 Rtn = false; 3407 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3408 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3409 { 3410 XLevel.Sectors[SecNum].Damage = Arg2; 3411 //FIXME Arg3 is MOD 3412 Rtn = true; 3413 } 3414 return Rtn; 3415} 3416 3417//========================================================================== 3418// 3419// EV_SectorSetGravity 3420// 3421//========================================================================== 3422 3423final bool EV_SectorSetGravity(int Arg1, int Arg2, int Arg3, int Arg4, 3424 int Arg5) 3425{ 3426 int SecNum; 3427 float SecGrav; 3428 bool Ret; 3429 3430 if (Arg3 > 99) 3431 Arg3 = 99; 3432 3433 SecGrav = itof(Arg2) + itof(Arg3) * 0.01; 3434 Ret = false; 3435 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3436 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3437 { 3438 XLevel.Sectors[SecNum].Gravity = SecGrav; 3439 Ret = true; 3440 } 3441 return Ret; 3442} 3443 3444//========================================================================== 3445// 3446// SetSectorFriction 3447// 3448//========================================================================== 3449 3450final bool EV_SectorSetFriction(int Arg1, int Arg2, int Arg3, int Arg4, 3451 int Arg5) 3452{ 3453 int s; 3454 int OldFriction; 3455 int OldMoveFactor; 3456 float Friction; 3457 float MoveFactor; 3458 bool Ret; 3459 3460 // An amount of 100 should result in a friction of 3461 // ORIG_FRICTION (0xE800) 3462 OldFriction = (0x1EB8 * Arg2) / 0x80 + 0xD001; 3463 3464 // killough 8/28/98: prevent odd situations 3465 if (OldFriction > 0x10000) 3466 OldFriction = 0x10000; 3467 if (OldFriction < 0) 3468 OldFriction = 0; 3469 3470 // The following check might seem odd. At the time of movement, 3471 // the move distance is multiplied by 'friction/0x10000', so a 3472 // higher friction value actually means 'less friction'. 3473 3474 // [RH] Twiddled these values so that momentum on ice (with 3475 // friction 0xf900) is the same as in Heretic/Hexen. 3476 if (OldFriction >= 0xe800) // ice 3477// movefactor = ((0x10092 - friction)*(0x70))/0x158; 3478 OldMoveFactor = ((0x10092 - OldFriction) * 1024) / 4352 + 568; 3479 else 3480 OldMoveFactor = ((OldFriction - 0xDB34) * (0xA)) / 0x80; 3481 3482 // killough 8/28/98: prevent odd situations 3483 if (OldMoveFactor < 32) 3484 OldMoveFactor = 32; 3485 3486 Friction = (1.0 - itof(OldFriction) / itof(0x10000)) * 35.0; 3487 MoveFactor = itof(OldMoveFactor) / itof(0x10000); 3488 3489 Ret = false; 3490 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3491 s = XLevel.FindSectorFromTag(Arg1, s)) 3492 { 3493 // killough 8/28/98: 3494 // 3495 // Instead of spawning thinkers, which are slow and expensive, 3496 // modify the sector's own friction values. Friction should be 3497 // a property of sectors, not objects which reside inside them. 3498 // Original code scanned every object in every friction sector 3499 // on every tic, adjusting its friction, putting unnecessary 3500 // drag on CPU. New code adjusts friction of sector only once 3501 // at level startup, and then uses this friction value. 3502 3503 XLevel.Sectors[s].Friction = Friction; 3504 XLevel.Sectors[s].MoveFactor = MoveFactor; 3505 // When used inside a script, the sectors' friction flags 3506 // can be enabled and disabled at will. 3507 if (OldFriction == 0xe800) 3508 { 3509 XLevel.Sectors[s].special &= ~SECSPEC_FRICTION_MASK; 3510 } 3511 else 3512 { 3513 XLevel.Sectors[s].special |= SECSPEC_FRICTION_MASK; 3514 } 3515 Ret = true; 3516 } 3517 return Ret; 3518} 3519 3520//========================================================================= 3521// 3522// EV_SectorChangeFlags 3523// 3524//========================================================================= 3525 3526final bool EV_SectorChangeFlags(int Arg1, int Arg2, int Arg3, int Arg4, 3527 int Arg5) 3528{ 3529 int SecNum; 3530 bool Rtn; 3531 3532 if (!Arg1) 3533 { 3534 return false; 3535 } 3536 Rtn = false; 3537 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 3538 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 3539 { 3540 sector_t* Sec = &XLevel.Sectors[SecNum]; 3541 if (Arg3 & SECF_SILENT) 3542 { 3543 Sec->bSilent = false; 3544 } 3545 else if (Arg2 & SECF_SILENT) 3546 { 3547 Sec->bSilent = true; 3548 } 3549 if (Arg3 & SECF_NOFALLINGDAMAGE) 3550 { 3551 Sec->bNoFallingDamage = false; 3552 } 3553 else if (Arg2 & SECF_NOFALLINGDAMAGE) 3554 { 3555 Sec->bNoFallingDamage = true; 3556 } 3557 Rtn = true; 3558 } 3559 return Rtn; 3560} 3561 3562//========================================================================== 3563// 3564// EV_SectorSetFloorPanning 3565// 3566//========================================================================== 3567 3568final bool EV_SectorSetFloorPanning(int Arg1, int Arg2, int Arg3, int Arg4, 3569 int Arg5) 3570{ 3571 int s; 3572 float XOffs = itof(Arg2) + itof(Arg3) / 100.0; 3573 float YOffs = itof(Arg4) + itof(Arg5) / 100.0; 3574 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3575 s = XLevel.FindSectorFromTag(Arg1, s)) 3576 { 3577 XLevel.Sectors[s].floor.xoffs = XOffs; 3578 XLevel.Sectors[s].floor.yoffs = YOffs; 3579 ClampSecPlaneOffsets(&XLevel.Sectors[s].floor); 3580 } 3581 return true; 3582} 3583 3584//========================================================================== 3585// 3586// EV_SectorSetCeilingPanning 3587// 3588//========================================================================== 3589 3590final bool EV_SectorSetCeilingPanning(int Arg1, int Arg2, int Arg3, int Arg4, 3591 int Arg5) 3592{ 3593 int s; 3594 float XOffs = itof(Arg2) + itof(Arg3) / 100.0; 3595 float YOffs = itof(Arg4) + itof(Arg5) / 100.0; 3596 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3597 s = XLevel.FindSectorFromTag(Arg1, s)) 3598 { 3599 XLevel.Sectors[s].ceiling.xoffs = XOffs; 3600 XLevel.Sectors[s].ceiling.yoffs = YOffs; 3601 ClampSecPlaneOffsets(&XLevel.Sectors[s].ceiling); 3602 } 3603 return true; 3604} 3605 3606//========================================================================== 3607// 3608// EV_SectorSetRotation 3609// 3610//========================================================================== 3611 3612final bool EV_SectorSetRotation(int Arg1, int Arg2, int Arg3, int Arg4, 3613 int Arg5) 3614{ 3615 int s; 3616 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3617 s = XLevel.FindSectorFromTag(Arg1, s)) 3618 { 3619 XLevel.Sectors[s].floor.Angle = itof(Arg2); 3620 XLevel.Sectors[s].ceiling.Angle = itof(Arg3); 3621 } 3622 return true; 3623} 3624 3625//========================================================================== 3626// 3627// EV_SectorSetFloorScale 3628// 3629//========================================================================== 3630 3631final bool EV_SectorSetFloorScale(int Arg1, int Arg2, int Arg3, int Arg4, 3632 int Arg5) 3633{ 3634 int s; 3635 float XScale = itof(Arg2) + itof(Arg3) / 100.0; 3636 float YScale = itof(Arg4) + itof(Arg5) / 100.0; 3637 if (XScale) 3638 { 3639 XScale = 1.0 / XScale; 3640 } 3641 if (YScale) 3642 { 3643 YScale = 1.0 / YScale; 3644 } 3645 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3646 s = XLevel.FindSectorFromTag(Arg1, s)) 3647 { 3648 if (XScale) 3649 { 3650 XLevel.Sectors[s].floor.XScale = XScale; 3651 } 3652 if (YScale) 3653 { 3654 XLevel.Sectors[s].floor.YScale = YScale; 3655 } 3656 } 3657 return true; 3658} 3659 3660//========================================================================== 3661// 3662// EV_SectorSetCeilingScale 3663// 3664//========================================================================== 3665 3666final bool EV_SectorSetCeilingScale(int Arg1, int Arg2, int Arg3, int Arg4, 3667 int Arg5) 3668{ 3669 int s; 3670 float XScale = itof(Arg2) + itof(Arg3) / 100.0; 3671 float YScale = itof(Arg4) + itof(Arg5) / 100.0; 3672 if (XScale) 3673 { 3674 XScale = 1.0 / XScale; 3675 } 3676 if (YScale) 3677 { 3678 YScale = 1.0 / YScale; 3679 } 3680 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3681 s = XLevel.FindSectorFromTag(Arg1, s)) 3682 { 3683 if (XScale) 3684 { 3685 XLevel.Sectors[s].ceiling.XScale = XScale; 3686 } 3687 if (YScale) 3688 { 3689 XLevel.Sectors[s].ceiling.YScale = YScale; 3690 } 3691 } 3692 return true; 3693} 3694 3695//========================================================================== 3696// 3697// EV_SectorSetFloorScale2 3698// 3699//========================================================================== 3700 3701final bool EV_SectorSetFloorScale2(int Arg1, int Arg2, int Arg3, int Arg4, 3702 int Arg5) 3703{ 3704 int s; 3705 float XScale = itof(Arg2) / itof(0x10000); 3706 float YScale = itof(Arg3) / itof(0x10000); 3707 if (XScale) 3708 { 3709 XScale = 1.0 / XScale; 3710 } 3711 if (YScale) 3712 { 3713 YScale = 1.0 / YScale; 3714 } 3715 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3716 s = XLevel.FindSectorFromTag(Arg1, s)) 3717 { 3718 if (XScale) 3719 { 3720 XLevel.Sectors[s].floor.XScale = XScale; 3721 } 3722 if (YScale) 3723 { 3724 XLevel.Sectors[s].floor.YScale = YScale; 3725 } 3726 } 3727 return true; 3728} 3729 3730//========================================================================== 3731// 3732// EV_SectorSetCeilingScale2 3733// 3734//========================================================================== 3735 3736final bool EV_SectorSetCeilingScale2(int Arg1, int Arg2, int Arg3, int Arg4, 3737 int Arg5) 3738{ 3739 int s; 3740 float XScale = itof(Arg2) / itof(0x10000); 3741 float YScale = itof(Arg3) / itof(0x10000); 3742 if (XScale) 3743 { 3744 XScale = 1.0 / XScale; 3745 } 3746 if (YScale) 3747 { 3748 YScale = 1.0 / YScale; 3749 } 3750 for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0; 3751 s = XLevel.FindSectorFromTag(Arg1, s)) 3752 { 3753 if (XScale) 3754 { 3755 XLevel.Sectors[s].ceiling.XScale = XScale; 3756 } 3757 if (YScale) 3758 { 3759 XLevel.Sectors[s].ceiling.YScale = YScale; 3760 } 3761 } 3762 return true; 3763} 3764 3765//========================================================================== 3766// 3767// EV_LineAlignFloor 3768// 3769//========================================================================== 3770 3771final bool EV_LineAlignFloor(int Arg1, int Arg2, int Arg3, int Arg4, 3772 int Arg5) 3773{ 3774 int Searcher; 3775 line_t* Line; 3776 bool Ret; 3777 sector_t* Sec; 3778 3779 Searcher = -1; 3780 Ret = false; 3781 for (Line = XLevel.FindLine(Arg1, &Searcher); Line; 3782 Line = XLevel.FindLine(Arg1, &Searcher)) 3783 { 3784 Sec = Arg2 ? Line->backsector : Line->frontsector; 3785 if (!Sec) 3786 { 3787 continue; 3788 } 3789 3790 TVec v1 = *Line->v1; 3791 3792 float Angle = atan2(Line->v2->y - v1.y, Line->v2->x - v1.x); 3793 float Norm = Angle - 90.0; 3794 3795 float Dist = -(cos(Norm) * v1.x + sin(Norm) * v1.y); 3796 3797 if (Arg2) 3798 { 3799 Angle = AngleMod360(Angle + 180.0); 3800 Dist = -Dist; 3801 } 3802 3803 Sec->floor.BaseAngle = Angle; 3804 while (Dist < 0.0) 3805 { 3806 Dist += 256.0; 3807 } 3808 while (Dist >= 256.0) 3809 { 3810 Dist -= 256.0; 3811 } 3812 Sec->floor.BaseYOffs = Dist; 3813 Ret = true; 3814 } 3815 return Ret; 3816} 3817 3818//========================================================================== 3819// 3820// EV_LineAlignCeiling 3821// 3822//========================================================================== 3823 3824final bool EV_LineAlignCeiling(int Arg1, int Arg2, int Arg3, int Arg4, 3825 int Arg5) 3826{ 3827 int Searcher; 3828 line_t* Line; 3829 bool Ret; 3830 sector_t* Sec; 3831 3832 Searcher = -1; 3833 Ret = false; 3834 for (Line = XLevel.FindLine(Arg1, &Searcher); Line; 3835 Line = XLevel.FindLine(Arg1, &Searcher)) 3836 { 3837 Sec = Arg2 ? Line->backsector : Line->frontsector; 3838 if (!Sec) 3839 { 3840 continue; 3841 } 3842 3843 TVec v1 = *Line->v1; 3844 3845 float Angle = atan2(Line->v2->y - v1.y, Line->v2->x - v1.x); 3846 float Norm = Angle - 90.0; 3847 3848 float Dist = -(cos(Norm) * v1.x + sin(Norm) * v1.y); 3849 3850 if (Arg2) 3851 { 3852 Angle = AngleMod360(Angle + 180.0); 3853 Dist = -Dist; 3854 } 3855 3856 Sec->ceiling.BaseAngle = Angle; 3857 while (Dist < 0.0) 3858 { 3859 Dist += 256.0; 3860 } 3861 while (Dist >= 256.0) 3862 { 3863 Dist -= 256.0; 3864 } 3865 Sec->ceiling.BaseYOffs = Dist; 3866 Ret = true; 3867 } 3868 return Ret; 3869} 3870 3871//========================================================================== 3872// 3873// EV_LineSetTextureOffset 3874// 3875//========================================================================== 3876 3877final bool EV_LineSetTextureOffset(int Arg1, int Arg2, int Arg3, int Arg4, 3878 int Arg5) 3879{ 3880 int Searcher; 3881 line_t* Line; 3882 3883 // Check if side is valid. 3884 if (Arg4 < 0 || Arg4 > 1) 3885 { 3886 return false; 3887 } 3888 3889 int NoChange = 0x7fff0000; 3890 3891 Searcher = -1; 3892 for (Line = XLevel.FindLine(Arg1, &Searcher); Line; 3893 Line = XLevel.FindLine(Arg1, &Searcher)) 3894 { 3895 if (Line->sidenum[Arg4] < 0) 3896 { 3897 continue; 3898 } 3899 side_t* Side = &XLevel.Sides[Line->sidenum[Arg4]]; 3900 3901 // X offset 3902 if (Arg2 != NoChange) 3903 { 3904 float Offs = itof(Arg2) / itof(0x10000); 3905 if (!(Arg5 & 8)) 3906 { 3907 // Set 3908 if (Arg5 & 1) 3909 { 3910 Side->TopTextureOffset = Offs; 3911 } 3912 if (Arg5 & 2) 3913 { 3914 Side->MidTextureOffset = Offs; 3915 } 3916 if (Arg5 & 4) 3917 { 3918 Side->BotTextureOffset = Offs; 3919 } 3920 } 3921 else 3922 { 3923 // Add 3924 if (Arg5 & 1) 3925 { 3926 Side->TopTextureOffset += Offs; 3927 } 3928 if (Arg5 & 2) 3929 { 3930 Side->MidTextureOffset += Offs; 3931 } 3932 if (Arg5 & 4) 3933 { 3934 Side->BotTextureOffset += Offs; 3935 } 3936 } 3937 } 3938 3939 // Y offset 3940 if (Arg3 != NoChange) 3941 { 3942 float Offs = itof(Arg3) / itof(0x10000); 3943 if (!(Arg5 & 8)) 3944 { 3945 // Set 3946 if (Arg5 & 1) 3947 { 3948 Side->TopRowOffset = Offs; 3949 } 3950 if (Arg5 & 2) 3951 { 3952 Side->MidRowOffset = Offs; 3953 } 3954 if (Arg5 & 4) 3955 { 3956 Side->BotRowOffset = Offs; 3957 } 3958 } 3959 else 3960 { 3961 // Add 3962 if (Arg5 & 1) 3963 { 3964 Side->TopRowOffset += Offs; 3965 } 3966 if (Arg5 & 2) 3967 { 3968 Side->MidRowOffset += Offs; 3969 } 3970 if (Arg5 & 4) 3971 { 3972 Side->BotRowOffset += Offs; 3973 } 3974 } 3975 } 3976 ClampSideOffsets(Side); 3977 } 3978 return true; 3979} 3980 3981//========================================================================== 3982// 3983// ClampSideOffsets 3984// 3985//========================================================================== 3986 3987final void ClampSideOffsets(side_t* Side) 3988{ 3989 if (Side->TopTextureOffset > 32767.0 || Side->TopTextureOffset < -32768.0) 3990 { 3991 Side->TopTextureOffset = 0.0; 3992 } 3993 if (Side->BotTextureOffset > 32767.0 || Side->BotTextureOffset < -32768.0) 3994 { 3995 Side->BotTextureOffset = 0.0; 3996 } 3997 if (Side->MidTextureOffset > 32767.0 || Side->MidTextureOffset < -32768.0) 3998 { 3999 Side->MidTextureOffset = 0.0; 4000 } 4001 if (Side->TopRowOffset > 32767.0 || Side->TopRowOffset < -32768.0) 4002 { 4003 Side->TopRowOffset = 0.0; 4004 } 4005 if (Side->BotRowOffset > 32767.0 || Side->BotRowOffset < -32768.0) 4006 { 4007 Side->BotRowOffset = 0.0; 4008 } 4009 if (Side->MidRowOffset > 32767.0 || Side->MidRowOffset < -32768.0) 4010 { 4011 Side->MidRowOffset = 0.0; 4012 } 4013} 4014 4015//========================================================================== 4016// 4017// ClampSecPlaneOffsets 4018// 4019//========================================================================== 4020 4021final void ClampSecPlaneOffsets(sec_plane_t* Plane) 4022{ 4023 if (Plane->xoffs > 32767.0 || Plane->xoffs < -32768.0) 4024 { 4025 Plane->xoffs = 0.0; 4026 } 4027 if (Plane->yoffs > 32767.0 || Plane->yoffs < -32768.0) 4028 { 4029 Plane->yoffs = 0.0; 4030 } 4031} 4032 4033//************************************************************************** 4034// 4035// Thing line specials 4036// 4037//************************************************************************** 4038 4039//========================================================================== 4040// 4041// EV_ThingProjectile 4042// 4043//========================================================================== 4044 4045final bool EV_ThingProjectile(int Arg1, int Arg2, int Arg3, int Arg4, 4046 int Arg5, bool gravity, int newtid, name TypeName, Entity Activator) 4047{ 4048 return DoThingProjectile(Arg1, Arg2, Arg3, Arg4, Arg5, gravity, newtid, 4049 TypeName, Activator, 0, none, false); 4050} 4051 4052//========================================================================== 4053// 4054// EV_ThingProjectileAimed 4055// 4056//========================================================================== 4057 4058final bool EV_ThingProjectileAimed(int Arg1, int Arg2, int Arg3, int Arg4, 4059 int Arg5, Entity Activator) 4060{ 4061 return DoThingProjectile(Arg1, Arg2, 0, Arg3, 0, 0, Arg5, '', Activator, 4062 Arg4, Activator, false); 4063} 4064 4065//========================================================================== 4066// 4067// EV_ThingProjectileIntercept 4068// 4069//========================================================================== 4070 4071final bool EV_ThingProjectileIntercept(int Arg1, int Arg2, int Arg3, int Arg4, 4072 int Arg5, Entity Activator) 4073{ 4074 return DoThingProjectile(Arg1, Arg2, 0, Arg3, 0, 0, Arg5, '', Activator, 4075 Arg4, Activator, true); 4076} 4077 4078//========================================================================== 4079// 4080// DoThingProjectile 4081// 4082//========================================================================== 4083 4084final bool DoThingProjectile(int tid, int TypeID, int AAngle, int ASpeed, 4085 int AVSpeed, bool gravity, int newtid, name TypeName, Entity Activator, 4086 int DestTID, Entity ForceDest, bool Intercept) 4087{ 4088 float angle; 4089 float speed; 4090 float vspeed; 4091 class<EntityEx> moType; 4092 Entity A; 4093 EntityEx newA; 4094 bool success; 4095 4096 success = false; 4097 if (TypeName) 4098 { 4099 moType = class<EntityEx>(FindClassLowerCase(TypeName)); 4100 } 4101 else 4102 { 4103 moType = class<EntityEx>(FindClassFromScriptId(TypeID, 4104 LineSpecialGameInfo(Game).GameFilterFlag)); 4105 } 4106 if (!moType) 4107 { 4108 return false; 4109 } 4110 moType = class<EntityEx>(GetClassReplacement(moType)); 4111 if (Level.Game.nomonsters && moType.default.bMonster) 4112 { 4113 return false; 4114 } 4115 angle = itof(AAngle) * (360.0 / 256.0); 4116 speed = itof(ASpeed) / 8.0; 4117 vspeed = itof(AVSpeed) / 8.0; 4118 for (A = tid ? FindMobjFromTID(tid, none) : Activator; A; 4119 A = FindMobjFromTID(tid, A)) 4120 { 4121 Entity Targ = ForceDest; 4122 if (DestTID) 4123 { 4124 Targ = FindMobjFromTID(DestTID, none); 4125 if (!Targ) 4126 { 4127 continue; 4128 } 4129 } 4130 4131 newA = Spawn(moType, A.Origin); 4132 if (newA.SightSound) 4133 { 4134 newA.PlaySound(newA.SightSound, CHAN_VOICE); 4135 } 4136 newA.Target = EntityEx(A); // Originator 4137 if (Targ) 4138 { 4139 TVec TargOrg = Targ.Origin + vector(0.0, 0.0, Targ.Height / 2.0); 4140 TVec Delta = TargOrg - newA.Origin; 4141 TVec TargVel = Targ.Velocity; 4142 if (!Targ.bNoGravity && Targ.WaterLevel < 3) 4143 { 4144 // If the target is subject to gravity and not underwater, 4145 // assume that it isn't moving vertically. Thanks to gravity, 4146 // even if we did consider the vertical component of the target's 4147 // velocity, we would still miss more often than not. 4148 TargVel.z = 0.0; 4149 } 4150 4151 if (Intercept && speed > 0.0 && (TargVel.x || TargVel.y || TargVel.z)) 4152 { 4153 // Aiming at the target's position some time in the future 4154 // is basically just an application of the law of sines: 4155 // a/sin(A) = b/sin(B) 4156 4157 float Dist = Length(Delta); 4158 float TargSpeed = Length(TargVel) / 35.0; 4159 float YDotX = DotProduct(-Delta, TargVel) / 35.0; 4160 float a = acos(FClamp(YDotX / TargSpeed / Dist, -1.0, 1.0)); 4161 float Multiplier = (Random() - Random()) * 0.1 + 1.1; 4162 float SinB = FClamp(TargSpeed * Multiplier * sin(a) / speed, -1.0, 1.0); 4163 4164 // Use the cross product of two of the triangle's sides to 4165 // get a rotation vector. 4166 TVec rv = CrossProduct(TargVel, Delta); 4167 // The vector must be normalized. 4168 rv = Normalise(rv); 4169 // Rotate around this vector. 4170 TVec AimVec = RotateVectorAroundVector(Delta, rv, asin(SinB)); 4171 // And make the projectile follow that vector at the desired speed. 4172 float AimScale = speed / Dist * 35.0; 4173 newA.Velocity = AimVec * AimScale; 4174 newA.Angles.yaw = atan2(newA.Velocity.y, newA.Velocity.x); 4175 } 4176 else 4177 { 4178 newA.Angles.yaw = atan2(Delta.y, Delta.x); 4179 newA.Velocity = Normalise(Delta) * speed * 35.0; 4180 } 4181 if (newA.bSeekerMissile) 4182 { 4183 newA.Tracer = EntityEx(Targ); 4184 } 4185 } 4186 else 4187 { 4188 newA.Angles.yaw = angle; 4189 newA.Velocity.x = speed * cos(angle) * 35.0; 4190 newA.Velocity.y = speed * sin(angle) * 35.0; 4191 newA.Velocity.z = vspeed * 35.0; 4192 } 4193 newA.bDropped = true; // Don't respawn 4194 if (gravity) 4195 { 4196 newA.bNoGravity = false; 4197 newA.Gravity = 0.125; 4198 } 4199 newA.SetTID(newtid); 4200 if (newA.bMissile) 4201 { 4202 if (newA.CheckMissileSpawn()) 4203 { 4204 success = true; 4205 } 4206 } 4207 else if (!newA.TestLocation()) 4208 { 4209 // If it counts as kill, adjust total kills count 4210 if (newA.CountsAsKill()) 4211 { 4212 TotalKills--; 4213 } 4214 // Same for items. 4215 if (newA.bCountItem) 4216 { 4217 TotalItems--; 4218 } 4219 newA.Destroy(); 4220 } 4221 else 4222 { 4223 success = true; 4224 } 4225 if (!tid) 4226 { 4227 break; 4228 } 4229 } 4230 return success; 4231} 4232 4233//========================================================================== 4234// 4235// EV_ThingSpawn 4236// 4237//========================================================================== 4238 4239final bool EV_ThingSpawn(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4240 bool fog, bool Facing, Entity Activator) 4241{ 4242 int tid; 4243 float angle; 4244 Entity A; 4245 EntityEx newAct; 4246 class<EntityEx> moType; 4247 bool success; 4248 4249 success = false; 4250 tid = Arg1; 4251 moType = class<EntityEx>(FindClassFromScriptId(Arg2, 4252 LineSpecialGameInfo(Game).GameFilterFlag)); 4253 if (!moType) 4254 { 4255 return false; 4256 } 4257 moType = class<EntityEx>(GetClassReplacement(moType)); 4258 if (Level.Game.nomonsters && moType.default.bMonster) 4259 { 4260 return false; 4261 } 4262 angle = itof(Arg3) * 360.0 / 256.0; 4263 for (A = tid ? FindMobjFromTID(tid, none) : Activator; A; 4264 A = FindMobjFromTID(tid, A)) 4265 { 4266 newAct = Spawn(moType, A.Origin); 4267 if (newAct.bFloatBob) 4268 { 4269 newAct.Origin.z = A.Origin.z - A.FloorZ; 4270 newAct.SetOrigin2(newAct.Origin); 4271 } 4272 if (newAct.TestLocation() == false) 4273 { 4274 // Didn't fit 4275 // If it counts as kill, adjust total kills count 4276 if (newAct.CountsAsKill()) 4277 { 4278 TotalKills--; 4279 } 4280 // Same for items. 4281 if (newAct.bCountItem) 4282 { 4283 TotalItems--; 4284 } 4285 newAct.Destroy(); 4286 } 4287 else 4288 { 4289 newAct.Angles.yaw = Facing ? A.Angles.yaw : angle; 4290 newAct.SetTID(Arg4); 4291 if (fog == true) 4292 { 4293 Spawn(TeleportFog, A.Origin + vector(0.0, 0.0, 4294 LineSpecialGameInfo(Game).TeleFogHeight)); 4295 } 4296 newAct.bDropped = true; // Don't respawn 4297 if (newAct.bFloatBob) 4298 { 4299 newAct.Special1f = newAct.Origin.z - newAct.FloorZ; 4300 } 4301 success = true; 4302 } 4303 if (!tid) 4304 { 4305 break; 4306 } 4307 } 4308 return success; 4309} 4310 4311//========================================================================== 4312// 4313// AcsSpawnThing 4314// 4315//========================================================================== 4316 4317final int AcsSpawnThing(name Name, TVec Org, int Tid, float Angle) 4318{ 4319 class EClass = FindClassLowerCase(Name); 4320 if (!EClass) 4321 { 4322 print("No such class %n", Name); 4323 return false; 4324 } 4325 class<EntityEx> EntClass = class<EntityEx>(EClass); 4326 if (!EntClass) 4327 { 4328 print("%n is not an actor class", Name); 4329 return false; 4330 } 4331 4332 EntityEx NewAct = Spawn(EntClass, Org); 4333 bool PrevPassMobj = NewAct.bPassMobj; 4334 NewAct.bPassMobj = true; 4335 if (NewAct.TestLocation() == false) 4336 { 4337 // Didn't fit 4338 // If it counts as kill, subtract total. 4339 if (NewAct.CountsAsKill()) 4340 { 4341 Level.TotalKills--; 4342 } 4343 // The same for items. 4344 if (NewAct.bCountItem) 4345 { 4346 Level.TotalItems--; 4347 } 4348 NewAct.Destroy(); 4349 return false; 4350 } 4351 NewAct.bPassMobj = PrevPassMobj; 4352 4353 NewAct.Angles.yaw = Angle; 4354 NewAct.SetTID(Tid); 4355 NewAct.bDropped = true; // Don't respawn 4356 return true; 4357} 4358 4359//========================================================================== 4360// 4361// AcsSpawnSpot 4362// 4363//========================================================================== 4364 4365final int AcsSpawnSpot(name Name, int SpotTid, int Tid, float Angle) 4366{ 4367 int NumSpawned = 0; 4368 Entity A; 4369 for (A = FindMobjFromTID(SpotTid, none); A; 4370 A = FindMobjFromTID(SpotTid, A)) 4371 { 4372 NumSpawned += AcsSpawnThing(Name, A.Origin, Tid, Angle); 4373 } 4374 return NumSpawned; 4375} 4376 4377//========================================================================== 4378// 4379// AcsSpawnSpotFacing 4380// 4381//========================================================================== 4382 4383final int AcsSpawnSpotFacing(name Name, int SpotTid, int Tid) 4384{ 4385 int NumSpawned = 0; 4386 Entity A; 4387 for (A = FindMobjFromTID(SpotTid, none); A; 4388 A = FindMobjFromTID(SpotTid, A)) 4389 { 4390 NumSpawned += AcsSpawnThing(Name, A.Origin, Tid, A.Angles.yaw); 4391 } 4392 return NumSpawned; 4393} 4394 4395//========================================================================== 4396// 4397// EV_ThingActivate 4398// 4399//========================================================================== 4400 4401final bool EV_ThingActivate(int tid, EntityEx Activator) 4402{ 4403 Entity A; 4404 bool success; 4405 4406 success = false; 4407 for (A = FindMobjFromTID(tid, none); A; A = FindMobjFromTID(tid, A)) 4408 { 4409 if (EntityEx(A).Activate(Activator)) 4410 { 4411 success = true; 4412 } 4413 } 4414 return success; 4415} 4416 4417//========================================================================== 4418// 4419// EV_ThingDeactivate 4420// 4421//========================================================================== 4422 4423final bool EV_ThingDeactivate(int tid, EntityEx Activator) 4424{ 4425 Entity A; 4426 bool success; 4427 4428 success = false; 4429 for (A = FindMobjFromTID(tid, none); A; A = FindMobjFromTID(tid, A)) 4430 { 4431 if (EntityEx(A).Deactivate(Activator)) 4432 { 4433 success = true; 4434 } 4435 } 4436 return success; 4437} 4438 4439//========================================================================== 4440// 4441// EV_ThingRemove 4442// 4443//========================================================================== 4444 4445final bool EV_ThingRemove(int tid) 4446{ 4447 Entity A; 4448 bool success; 4449 4450 success = false; 4451 for (A = FindMobjFromTID(tid, none); A; A = FindMobjFromTID(tid, A)) 4452 { 4453 // Be friendly to level statistics. 4454 if (EntityEx(A).CountsAsKill() && A.Health > 0) 4455 { 4456 TotalKills--; 4457 } 4458 if (EntityEx(A).bCountItem) 4459 { 4460 TotalItems--; 4461 } 4462 A.RemoveThing(); 4463 success = true; 4464 } 4465 return success; 4466} 4467 4468//========================================================================== 4469// 4470// EV_ThingDestroy 4471// 4472//========================================================================== 4473 4474final bool EV_ThingDestroy(int tid, int Extreme) 4475{ 4476 EntityEx A; 4477 bool success; 4478 4479 success = false; 4480 for (A = EntityEx(FindMobjFromTID(tid, none)); A; 4481 A = EntityEx(FindMobjFromTID(tid, A))) 4482 { 4483 if (A.bShootable) 4484 { 4485 A.Damage(none, none, Extreme ? 1000000 : A.Health, ''); 4486 success = true; 4487 } 4488 } 4489 return success; 4490} 4491 4492//=========================================================================== 4493// 4494// EV_HealThing 4495// 4496//=========================================================================== 4497 4498final bool EV_HealThing(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4499 EntityEx A) 4500{ 4501 if (A) 4502 { 4503 int Max = Arg2; 4504 4505 if (Max == 0 || !A.bIsPlayer) 4506 { 4507 A.GiveBody(Arg1); 4508 return true; 4509 } 4510 else if (Max == 1) 4511 { 4512 Max = 200; 4513 class<Health> SClass = class<Health>(FindClass('Soulsphere')); 4514 if (SClass) 4515 { 4516 Max = SClass.default.MaxAmount; 4517 } 4518 } 4519 4520 // If health is already above max, do nothing 4521 if (A.Health < Max) 4522 { 4523 A.Health += Arg1; 4524 if (A.Health > Max && Max > 0) 4525 { 4526 A.Health = Max; 4527 } 4528 A.Player.Health = A.Health; 4529 } 4530 } 4531 4532 return !!A; 4533} 4534 4535//========================================================================== 4536// 4537// EV_ThingDamage 4538// 4539//========================================================================== 4540 4541final int EV_ThingDamage(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4542 Entity Activator) 4543{ 4544 return DoThingDamage(Arg1, Arg2, ModToDamageType(Arg3), Activator); 4545} 4546 4547//========================================================================== 4548// 4549// DoThingDamage 4550// 4551//========================================================================== 4552 4553final int DoThingDamage(int Tid, int Amount, name DmgType, Entity Activator) 4554{ 4555 Entity A; 4556 int NumDamaged; 4557 4558 NumDamaged = 0; 4559 for (A = Tid ? FindMobjFromTID(Tid, none) : Activator; A; 4560 A = FindMobjFromTID(Tid, A)) 4561 { 4562 if (EntityEx(A).bShootable) 4563 { 4564 if (Amount > 0) 4565 { 4566 EntityEx(A).Damage(none, EntityEx(Activator), Amount, DmgType); 4567 } 4568 else if (A.Health < A.default.Health) 4569 { 4570 // Negative amount heals thing. 4571 A.Health -= Amount; 4572 if (A.Health > A.default.Health) 4573 { 4574 A.Health = A.default.Health; 4575 } 4576 if (A.bIsPlayer) 4577 { 4578 A.Player.Health = A.Health; 4579 } 4580 } 4581 NumDamaged++; 4582 } 4583 if (!Tid) 4584 { 4585 break; 4586 } 4587 } 4588 return NumDamaged; 4589} 4590 4591//=========================================================================== 4592// 4593// EV_ThingSetGoal 4594// 4595//=========================================================================== 4596 4597final bool EV_ThingSetGoal(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 4598{ 4599 Entity Ent; 4600 EntityEx NewGoal = none; 4601 for (Ent = FindMobjFromTID(Arg2, none); Ent; 4602 Ent = FindMobjFromTID(Arg2, Ent)) 4603 { 4604 if (!Ent.IsA('PatrolPoint')) 4605 { 4606 continue; 4607 } 4608 NewGoal = EntityEx(Ent); 4609 break; 4610 } 4611 4612 bool Ret = false; 4613 for (Ent = FindMobjFromTID(Arg1, none); Ent; 4614 Ent = FindMobjFromTID(Arg1, Ent)) 4615 { 4616 EntityEx E = EntityEx(Ent); 4617 Ret = true; 4618 if (E.bShootable) 4619 { 4620 E.Goal = NewGoal; 4621 E.bChaseGoal = !!Arg4; 4622 if (!E.Target) 4623 { 4624 E.ReactionTime = itof(Arg3); 4625 } 4626 } 4627 } 4628 return Ret; 4629} 4630 4631//=========================================================================== 4632// 4633// EV_ChangeCamera 4634// 4635//=========================================================================== 4636 4637final bool EV_ChangeCamera(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4638 EntityEx A) 4639{ 4640 Entity Camera = none; 4641 if (Arg1) 4642 { 4643 Camera = FindMobjFromTID(Arg1, none); 4644 } 4645 4646 if (!A || !A.Player || Arg2) 4647 { 4648 BasePlayer P; 4649 foreach AllActivePlayers(P) 4650 { 4651 if (Camera) 4652 { 4653 P.Camera = Camera; 4654 if (Arg3) 4655 { 4656 PlayerEx(P).bRevertCamera = true; 4657 } 4658 } 4659 else 4660 { 4661 P.Camera = P.MO; 4662 PlayerEx(P).bRevertCamera = false; 4663 } 4664 } 4665 } 4666 else 4667 { 4668 if (Camera) 4669 { 4670 A.Player.Camera = Camera; 4671 if (Arg3) 4672 { 4673 PlayerEx(A.Player).bRevertCamera = true; 4674 } 4675 } 4676 else 4677 { 4678 A.Player.Camera = A; 4679 PlayerEx(A.Player).bRevertCamera = false; 4680 } 4681 } 4682 return true; 4683} 4684 4685//=========================================================================== 4686// 4687// EV_ThingSetTranslation 4688// 4689//=========================================================================== 4690 4691final bool EV_ThingSetTranslation(int Arg1, int Arg2, int Arg3, int Arg4, 4692 int Arg5, EntityEx A) 4693{ 4694 int Trans; 4695 if (Arg2 == -1 && A) 4696 { 4697 Trans = A.Translation; 4698 } 4699 else if (Arg2 >= 1 && Arg2 < Level::MAX_LEVEL_TRANSLATIONS) 4700 { 4701 Trans = (Entity::TRANSL_Level << Entity::TRANSL_TYPE_SHIFT) + 4702 Arg2 - 1; 4703 } 4704 else 4705 { 4706 Trans = 0; 4707 } 4708 4709 bool Ret = false; 4710 if (Arg1) 4711 { 4712 Entity Ent; 4713 for (Ent = FindMobjFromTID(Arg1, none); Ent; 4714 Ent = FindMobjFromTID(Arg1, Ent)) 4715 { 4716 Ret = true; 4717 Ent.Translation = Trans ? Trans : Ent.default.Translation; 4718 } 4719 } 4720 else if (A) 4721 { 4722 Ret = true; 4723 A.Translation = Trans ? Trans : A.default.Translation; 4724 } 4725 return Ret; 4726} 4727 4728//=========================================================================== 4729// 4730// EV_ThingHate 4731// 4732//=========================================================================== 4733 4734final bool EV_ThingHate(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4735 EntityEx A) 4736{ 4737 EntityEx Hatee = none; 4738 bool NothingToHate = false; 4739 if (Arg2) 4740 { 4741 for (Hatee = EntityEx(FindMobjFromTID(Arg2, none)); Hatee; 4742 Hatee = EntityEx(FindMobjFromTID(Arg2, Hatee))) 4743 { 4744 if (Hatee.bShootable && // can't hate nonshootable things 4745 Hatee.Health > 0 && // can't hate dead things 4746 !Hatee.bDormant) // can't target dormant things 4747 { 4748 break; 4749 } 4750 } 4751 if (!Hatee) 4752 { 4753 // Nothing to hate 4754 NothingToHate = true; 4755 } 4756 } 4757 4758 EntityEx Hater; 4759 if (Arg1 == 0) 4760 { 4761 if (A && A.bIsPlayer) 4762 { 4763 // Players cannot have their attitudes set 4764 return false; 4765 } 4766 else 4767 { 4768 Hater = A; 4769 } 4770 } 4771 else 4772 { 4773 for (Hater = EntityEx(FindMobjFromTID(Arg1, none)); Hater; 4774 Hater = EntityEx(FindMobjFromTID(Arg1, Hater))) 4775 { 4776 if (Hater.Health > 0 && Hater.bShootable) 4777 { 4778 break; 4779 } 4780 } 4781 } 4782 4783 while (Hater) 4784 { 4785 // Can't hate if can't attack. 4786 if (Hater.SeeState) 4787 { 4788 // If hating a group of things, record the TID and NULL 4789 // the target (if its TID doesn't match). A_Look will 4790 // find an appropriate thing to go chase after. 4791 if (Arg3) 4792 { 4793 Hater.TIDToHate = Arg2; 4794 Hater.LastLookActor = none; 4795 4796 // If the TID to hate is 0, then don't forget the target and 4797 // lastenemy fields. 4798 if (Arg2) 4799 { 4800 if (Hater.Target && Hater.Target.TID != Arg2) 4801 { 4802 Hater.Target = none; 4803 } 4804 if (Hater.LastEnemy && Hater.LastEnemy.TID != Arg2) 4805 { 4806 Hater.LastEnemy = none; 4807 } 4808 } 4809 } 4810 // Hate types for Arg3: 4811 // 4812 // 0 - Just hate one specific actor 4813 // 1 - Hate actors with given TID and attack players when shot 4814 // 2 - Same as 1, but will go after enemies without seeing them first 4815 // 3 - Hunt actors with given TID and also players 4816 // 4 - Same as 3, but will go after monsters without seeing them first 4817 // 5 - Hate actors with given TID and ignore player attacks 4818 // 6 - Same as 5, but will go after enemies without seeing them first 4819 4820 // Note here: If you use Thing_Hate (tid, 0, 2), you can make 4821 // a monster go after a player without seeing him first. 4822 Hater.bNoSightCheck = Arg3 == 2 || Arg3 == 4 || Arg3 == 6; 4823 Hater.bHuntPlayers = Arg3 == 3 || Arg3 == 4; 4824 Hater.bNoHatePlayers = Arg3 == 5 || Arg3 == 6; 4825 4826 if (Arg2 == 0) 4827 { 4828 Hatee = A; 4829 } 4830 else if (NothingToHate) 4831 { 4832 Hatee = none; 4833 } 4834 else if (Arg3) 4835 { 4836 do 4837 { 4838 Hatee = EntityEx(FindMobjFromTID(Arg2, Hatee)); 4839 } 4840 while (!Hatee || 4841 Hatee == Hater || // can't hate self 4842 !Hatee.bShootable || // can't hate nonshootable things 4843 Hatee.Health <= 0 || // can't hate dead things 4844 Hatee.bDormant); 4845 } 4846 4847 if (Hatee && Hatee != Hater && (Arg3 == 0 || (Hater.Goal && Hater.Target != Hater.Goal))) 4848 { 4849 if (Hater.Target) 4850 { 4851 Hater.LastEnemy = Hater.Target; 4852 } 4853 Hater.Target = Hatee; 4854 if (!Hater.bDormant) 4855 { 4856 if (Hater.Health > 0) 4857 { 4858 Hater.SetState(Hater.SeeState); 4859 } 4860 } 4861 } 4862 } 4863 if (Arg1) 4864 { 4865 for (Hater = EntityEx(FindMobjFromTID(Arg1, Hater)); Hater; 4866 Hater = EntityEx(FindMobjFromTID(Arg1, Hater))) 4867 { 4868 if (Hater.Health > 0 && Hater.bShootable) 4869 { 4870 break; 4871 } 4872 } 4873 } 4874 else 4875 { 4876 Hater = none; 4877 } 4878 } 4879 return true; 4880} 4881 4882//=========================================================================== 4883// 4884// EV_ThingMove 4885// 4886//=========================================================================== 4887 4888final bool EV_ThingMove(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4889 EntityEx A) 4890{ 4891 if (Arg1) 4892 { 4893 A = EntityEx(FindMobjFromTID(Arg1, none)); 4894 } 4895 4896 EntityEx Dest = EntityEx(FindMobjFromTID(Arg2, none)); 4897 4898 if (A && Dest) 4899 { 4900 TVec OldOrg = A.Origin; 4901 A.SetOrigin(Dest.Origin); 4902 if (A.TestLocation()) 4903 { 4904 if (Arg3) 4905 { 4906 Spawn(TeleportFog, OldOrg + vector(0.0, 0.0, 4907 LineSpecialGameInfo(Game).TeleFogHeight)); 4908 Spawn(TeleportFog, A.Origin + vector(0.0, 0.0, 4909 LineSpecialGameInfo(Game).TeleFogHeight)); 4910 } 4911 return true; 4912 } 4913 else 4914 { 4915 A.SetOrigin(OldOrg); 4916 return false; 4917 } 4918 } 4919 return false; 4920} 4921 4922//=========================================================================== 4923// 4924// EV_ThingSetSpecial 4925// 4926// It's recomended to use SetThingSpecial ACS command that can set all 5 4927// arguments. 4928// 4929//=========================================================================== 4930 4931final bool EV_ThingSetSpecial(int Arg1, int Arg2, int Arg3, int Arg4, 4932 int Arg5, EntityEx A) 4933{ 4934 if (Arg1) 4935 { 4936 Entity Ent; 4937 for (Ent = FindMobjFromTID(Arg1, none); Ent; 4938 Ent = FindMobjFromTID(Arg1, Ent)) 4939 { 4940 Ent.Special = Arg2; 4941 Ent.Args[0] = Arg3; 4942 Ent.Args[1] = Arg4; 4943 Ent.Args[2] = Arg5; 4944 } 4945 } 4946 else if (A) 4947 { 4948 A.Special = Arg2; 4949 A.Args[0] = Arg3; 4950 A.Args[1] = Arg4; 4951 A.Args[2] = Arg5; 4952 } 4953 return true; 4954} 4955 4956//=========================================================================== 4957// 4958// EV_ThrustThingZ 4959// 4960//=========================================================================== 4961 4962final bool EV_ThrustThingZ(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 4963 EntityEx A) 4964{ 4965 float Force = itof(Arg2) * 35.0 / 4.0; 4966 if (Arg3) 4967 { 4968 Force = -Force; 4969 } 4970 4971 if (Arg1) 4972 { 4973 Entity Ent; 4974 for (Ent = FindMobjFromTID(Arg1, none); Ent; 4975 Ent = FindMobjFromTID(Arg1, Ent)) 4976 { 4977 if (!Arg4) 4978 { 4979 Ent.Velocity.z = Force; 4980 } 4981 else 4982 { 4983 Ent.Velocity.z += Force; 4984 } 4985 } 4986 return true; 4987 } 4988 if (A) 4989 { 4990 if (!Arg4) 4991 { 4992 A.Velocity.z = Force; 4993 } 4994 else 4995 { 4996 A.Velocity.z += Force; 4997 } 4998 return true; 4999 } 5000 return false; 5001} 5002 5003//=========================================================================== 5004// 5005// EV_ThingChangeTID 5006// 5007//=========================================================================== 5008 5009final bool EV_ThingChangeTID(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 5010 EntityEx A) 5011{ 5012 if (!Arg1) 5013 { 5014 if (A) 5015 { 5016 A.SetTID(Arg2); 5017 } 5018 } 5019 else 5020 { 5021 Entity Ent; 5022 for (Ent = FindMobjFromTID(Arg1, none); Ent; 5023 Ent = FindMobjFromTID(Arg1, Ent)) 5024 { 5025 Ent.SetTID(Arg2); 5026 } 5027 } 5028 return true; 5029} 5030 5031//=========================================================================== 5032// 5033// EV_ThingStop 5034// 5035//=========================================================================== 5036 5037final bool EV_ThingStop(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 5038 EntityEx A) 5039{ 5040 bool Ret = false; 5041 if (!Arg1) 5042 { 5043 if (A) 5044 { 5045 A.Velocity = vector(0.0, 0.0, 0.0); 5046 Ret = true; 5047 } 5048 } 5049 else 5050 { 5051 Entity Ent; 5052 for (Ent = FindMobjFromTID(Arg1, none); Ent; 5053 Ent = FindMobjFromTID(Arg1, Ent)) 5054 { 5055 Ent.Velocity = vector(0.0, 0.0, 0.0); 5056 Ret = true; 5057 } 5058 } 5059 return Ret; 5060} 5061 5062//=========================================================================== 5063// 5064// EV_SetPlayerProperty 5065// 5066//=========================================================================== 5067 5068final bool EV_SetPlayerProperty(int Arg1, int Arg2, int Arg3, int Arg4, 5069 int Arg5, EntityEx A) 5070{ 5071 if (!Arg1) 5072 { 5073 if (!A || !A.bIsPlayer) 5074 { 5075 return false; 5076 } 5077 return A.SetPlayerProperty(Arg3, Arg2); 5078 } 5079 else 5080 { 5081 bool Ret = false; 5082 int i; 5083 for (i = 0; i < MAXPLAYERS; i++) 5084 { 5085 if (!Game.Players[i] || !Game.Players[i].bSpawned) 5086 { 5087 continue; 5088 } 5089 if (EntityEx(Game.Players[i].MO).SetPlayerProperty(Arg3, Arg2)) 5090 { 5091 Ret = true; 5092 } 5093 } 5094 return Ret; 5095 } 5096} 5097 5098//=========================================================================== 5099// 5100// EV_ThingRaise 5101// 5102//=========================================================================== 5103 5104final bool EV_ThingRaise(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 5105 EntityEx A) 5106{ 5107 bool Ret = false; 5108 if (!Arg1) 5109 { 5110 if (A) 5111 { 5112 Ret = DoThingRaise(A); 5113 } 5114 } 5115 else 5116 { 5117 Entity Ent; 5118 for (Ent = FindMobjFromTID(Arg1, none); Ent; 5119 Ent = FindMobjFromTID(Arg1, Ent)) 5120 { 5121 if (DoThingRaise(EntityEx(Ent))) 5122 { 5123 Ret = true; 5124 } 5125 } 5126 } 5127 return true; 5128} 5129 5130//=========================================================================== 5131// 5132// DoThingRaise 5133// 5134//=========================================================================== 5135 5136final bool DoThingRaise(EntityEx A) 5137{ 5138 if (!A.DoThingRaise()) 5139 { 5140 return false; 5141 } 5142 return true; 5143} 5144 5145//************************************************************************** 5146// 5147// Force field 5148// 5149//************************************************************************** 5150 5151//========================================================================== 5152// 5153// EV_ForceField 5154// 5155//========================================================================== 5156 5157final bool EV_ForceField(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 5158 EntityEx A) 5159{ 5160 A.Damage(none, none, 16, ''); 5161 A.Velocity.x += 7.8125 * cos(A.Angles.yaw + 180.0) * 35.0; 5162 A.Velocity.y += 7.8125 * sin(A.Angles.yaw + 180.0) * 35.0; 5163 return true; 5164} 5165 5166//========================================================================== 5167// 5168// EV_RemoveForceField 5169// 5170//========================================================================== 5171 5172final bool EV_RemoveForceField(int Arg1, int Arg2, int Arg3, int Arg4, 5173 int Arg5) 5174{ 5175 int i; 5176 int secnum; 5177 sector_t* sec; 5178 line_t* secline; 5179 5180 for (secnum = XLevel.FindSectorFromTag(Arg1, -1); secnum >= 0; 5181 secnum = XLevel.FindSectorFromTag(Arg1, secnum)) 5182 { 5183 sec = &XLevel.Sectors[secnum]; 5184 for (i = 0; i < sec->linecount; i++) 5185 { 5186 secline = sec->lines[i]; 5187 if (secline->special != LNSPEC_ForceField) 5188 continue; 5189 if (!(secline->flags & ML_TWOSIDED)) 5190 continue; 5191 5192 XLevel.Sides[secline->sidenum[0]].MidTexture = 0; 5193 XLevel.Sides[secline->sidenum[1]].MidTexture = 0; 5194 secline->special = 0; 5195 secline->flags &= ~ML_BLOCKING; 5196 } 5197 } 5198 return true; 5199} 5200 5201//************************************************************************** 5202// 5203// Teleportation 5204// 5205//************************************************************************** 5206 5207//========================================================================== 5208// 5209// EV_Teleport 5210// 5211//========================================================================== 5212 5213final bool EV_Teleport(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 5214 EntityEx thing, line_t* Line, bool fog) 5215{ 5216 int i; 5217 int count; 5218 EntityEx A; 5219 int SecTag; 5220 int SecNum; 5221 sector_t* Sec; 5222 bool SrcFog; 5223 bool KeepOrient; 5224 TVec DstOrg; 5225 float Angle; 5226 TVec OldVel; 5227 5228 if (!thing) 5229 { 5230 // Teleport function called with an invalid mobj 5231 return false; 5232 } 5233 if (thing.bNoTeleport) 5234 { 5235 return false; 5236 } 5237 5238 A = none; 5239 if (fog) 5240 { 5241 SecTag = Arg2; 5242 SrcFog = !Arg3; 5243 KeepOrient = false; 5244 } 5245 else 5246 { 5247 SecTag = Arg3; 5248 SrcFog = false; 5249 KeepOrient = !Arg2; 5250 } 5251 if (Arg1) 5252 { 5253 count = 0; 5254 for (A = EntityEx(FindMobjFromTID(Arg1, none)); A; 5255 A = EntityEx(FindMobjFromTID(Arg1, A))) 5256 { 5257 if (SecTag == 0 || A.Sector->tag == SecTag) 5258 { 5259 count++; 5260 } 5261 } 5262 if (count == 0) 5263 { 5264 return false; 5265 } 5266 count = 1 + (P_Random() % count); 5267 A = none; 5268 for (i = 0; i < count; i++) 5269 { 5270 do 5271 { 5272 A = EntityEx(FindMobjFromTID(Arg1, A)); 5273 } 5274 while (A && SecTag != 0 && A.Sector->tag != SecTag); 5275 } 5276 } 5277 else if (SecTag) 5278 { 5279 for (SecNum = XLevel.FindSectorFromTag(SecTag, -1); SecNum >= 0; 5280 SecNum = XLevel.FindSectorFromTag(SecTag, SecNum)) 5281 { 5282 Sec = &XLevel.Sectors[SecNum]; 5283 foreach AllThinkers(EntityEx, A) 5284 { 5285 if (!TeleportDest(A)) 5286 { 5287 // Not a teleportman 5288 continue; 5289 } 5290 if (A.Sector != Sec) 5291 { 5292 // Wrong sector 5293 continue; 5294 } 5295 break; 5296 } 5297 if (A) 5298 { 5299 break; 5300 } 5301 } 5302 } 5303 if (!A) 5304 { 5305 return false; 5306 } 5307 5308 DstOrg = A.Origin; 5309 // Lee Killough's changes for silent teleporters from BOOM 5310 if (KeepOrient && Line) 5311 { 5312 // Get the angle between the exit thing and source linedef. 5313 // Rotate 180 degrees, so that walking perpendicularly across 5314 // teleporter linedef causes thing to exit in the direction 5315 // indicated by the exit thing. 5316 Angle = atan2(Line->normal.y, Line->normal.x) - A.Angles.yaw + 180.0; 5317 5318 // Momentum of thing crossing teleporter linedef 5319 OldVel = thing.Velocity; 5320 } 5321 if (!TeleportDest2(A)) 5322 { 5323 DstOrg.z = EntityEx::ONFLOORZ; 5324 } 5325 if (thing.Teleport(DstOrg, A.Angles.yaw, fog, SrcFog, KeepOrient)) 5326 { 5327 // Lee Killough's changes for silent teleporters from BOOM 5328 if (!fog && Line && KeepOrient) 5329 { 5330 // Rotate thing according to difference in angles 5331 thing.Angles.yaw = AngleMod360(thing.Angles.yaw + Angle); 5332 5333 // Rotate thing's momentum to come out of exit just like it entered 5334 thing.Velocity.x = OldVel.x * cos(Angle) - OldVel.y * sin(Angle); 5335 thing.Velocity.y = OldVel.y * cos(Angle) + OldVel.x * sin(Angle); 5336 } 5337 return true; 5338 } 5339 return false; 5340} 5341 5342//========================================================================== 5343// 5344// EV_TeleportOther 5345// 5346// Teleport anything matching other_tid to dest_tid 5347// 5348//========================================================================== 5349 5350final bool EV_TeleportOther(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 5351{ 5352 bool Ret; 5353 EntityEx A; 5354 5355 Ret = false; 5356 if (Arg1 && Arg2) 5357 { 5358 for (A = EntityEx(FindMobjFromTID(Arg1, none)); A; 5359 A = EntityEx(FindMobjFromTID(Arg1, A))) 5360 { 5361 Ret |= EV_Teleport(Arg2, 0, 0, 0, 0, A, NULL, !!Arg3); 5362 } 5363 } 5364 return Ret; 5365} 5366 5367//========================================================================== 5368// 5369// DoGroupForOne 5370// 5371//========================================================================== 5372 5373final bool DoGroupForOne(EntityEx victim, EntityEx source, EntityEx dest, 5374 bool floorz, bool fog) 5375{ 5376 float an = dest.Angles.yaw - source.Angles.yaw; 5377 float offX = victim.Origin.x - source.Origin.x; 5378 float offY = victim.Origin.y - source.Origin.y; 5379 float offAngle = victim.Angles.yaw - source.Angles.yaw; 5380 float newX = offX * cos(an) - offY * sin(an); 5381 float newY = offX * sin(an) + offY * cos(an); 5382 bool res; 5383 5384 res = victim.Teleport(vector(dest.Origin.x + newX, 5385 dest.Origin.y + newY, floorz ? EntityEx::ONFLOORZ : dest.Origin.z + 5386 victim.Origin.z - source.Origin.z), 0.0, fog, fog, !fog); 5387 // P_Teleport only changes angle if fog is true 5388 victim.Angles.yaw = AngleMod360(dest.Angles.yaw + offAngle); 5389 5390 return res; 5391} 5392 5393//========================================================================== 5394// 5395// EV_TeleportGroup 5396// 5397// [RH] Teleport a group of actors centred around source_tid so 5398// that they become centred around dest_tid instead. 5399// 5400//========================================================================== 5401 5402final bool EV_TeleportGroup(int group_tid, int source_tid, int dest_tid, 5403 bool moveSource, bool fog, EntityEx victim) 5404{ 5405 EntityEx sourceOrigin; 5406 EntityEx destOrigin; 5407 bool didSomething; 5408 bool floorz; 5409 5410 sourceOrigin = EntityEx(FindMobjFromTID(source_tid, none)); 5411 if (!sourceOrigin) 5412 { 5413 // If there is no source origin, behave like TeleportOther 5414 return EV_TeleportOther(group_tid, dest_tid, fog, 0, 0); 5415 } 5416 5417 destOrigin = none; 5418 do 5419 { 5420 destOrigin = EntityEx(FindMobjFromTID(dest_tid, destOrigin)); 5421 } 5422 while (destOrigin && !TeleportDest(destOrigin)); 5423 if (!destOrigin) 5424 { 5425 return false; 5426 } 5427 5428 didSomething = false; 5429 floorz = !TeleportDest2(destOrigin); 5430 5431 // Use the passed victim if group_tid is 0 5432 if (group_tid == 0 && victim) 5433 { 5434 didSomething = DoGroupForOne(victim, sourceOrigin, destOrigin, 5435 floorz, fog); 5436 } 5437 else 5438 { 5439 // For each actor with tid matching arg0, move it to the same 5440 // position relative to destOrigin as it is relative to 5441 // sourceOrigin before the teleport. 5442 for (victim = EntityEx(FindMobjFromTID(group_tid, none)); victim; 5443 victim = EntityEx(FindMobjFromTID(group_tid, victim))) 5444 { 5445 didSomething |= DoGroupForOne(victim, sourceOrigin, destOrigin, 5446 floorz, fog); 5447 } 5448 } 5449 5450 if (moveSource && didSomething) 5451 { 5452 didSomething |= sourceOrigin.Teleport(vector(destOrigin.Origin.x, 5453 destOrigin.Origin.y, floorz ? EntityEx::ONFLOORZ : destOrigin.Origin.z), 5454 0.0, false, false, true); 5455 sourceOrigin.Angles.yaw = destOrigin.Angles.yaw; 5456 } 5457 5458 return didSomething; 5459} 5460 5461//========================================================================== 5462// 5463// EV_TeleportSector 5464// 5465// [RH] Teleport a group of actors in a sector. Source_tid is used as a 5466// reference point so that they end up in the same position relative to 5467// dest_tid. Group_tid can be used to not teleport all actors in the sector. 5468// 5469//========================================================================== 5470 5471final bool EV_TeleportSector(int tag, int source_tid, int dest_tid, bool fog, 5472 int group_tid) 5473{ 5474 EntityEx sourceOrigin; 5475 EntityEx destOrigin; 5476 bool didSomething; 5477 bool floorz; 5478 int secnum; 5479 sector_t* sec; 5480 EntityEx A; 5481 5482 sourceOrigin = EntityEx(FindMobjFromTID(source_tid, none)); 5483 if (!sourceOrigin) 5484 { 5485 return false; 5486 } 5487 5488 destOrigin = none; 5489 do 5490 { 5491 destOrigin = EntityEx(FindMobjFromTID(dest_tid, destOrigin)); 5492 } 5493 while (destOrigin && !TeleportDest(destOrigin)); 5494 if (!destOrigin) 5495 { 5496 return false; 5497 } 5498 5499 didSomething = false; 5500 floorz = !TeleportDest2(destOrigin); 5501 5502 for (secnum = XLevel.FindSectorFromTag(tag, -1); secnum >= 0; 5503 secnum = XLevel.FindSectorFromTag(tag, secnum)) 5504 { 5505 sec = &XLevel.Sectors[secnum]; 5506 5507 msecnode_t*Node = sec->TouchingThingList; 5508 while (Node) 5509 { 5510 A = EntityEx(Node->Thing); 5511 msecnode_t*Next = Node->SNext; 5512 // possibly limit actors by group 5513 if (A.Sector == sec && (group_tid == 0 || A.TID == group_tid)) 5514 { 5515 didSomething |= DoGroupForOne(A, sourceOrigin, destOrigin, 5516 floorz, fog); 5517 } 5518 Node = Next; 5519 } 5520 } 5521 return didSomething; 5522} 5523 5524//========================================================================== 5525// 5526// EV_SilentLineTeleport 5527// 5528// Silent linedef-based TELEPORTATION, by Lee Killough 5529// Primarily for rooms-over-rooms etc. 5530// This is the complete player-preserving kind of teleporter. 5531// It has advantages over the teleporter with thing exits. 5532// 5533// [RH] Modified to support different source and destination ids. 5534// 5535//========================================================================== 5536 5537final bool EV_SilentLineTeleport(line_t *line, int side, EntityEx thing, 5538 int id, bool reverse) 5539{ 5540 int searcher; 5541 line_t *l; 5542 5543 if (side || thing.bNoTeleport || !line) 5544 { 5545 return false; 5546 } 5547 5548 searcher = -1; 5549 for (l = XLevel.FindLine(id, &searcher); l; 5550 l = XLevel.FindLine(id, &searcher)) 5551 { 5552 TVec SrcXAxis; 5553 TVec SrcYAxis; 5554 TVec DstXAxis; 5555 TVec DstYAxis; 5556 TVec newPos; 5557 TVec TempV; 5558 TAVec TempA; 5559 float pos; 5560 float TempX; 5561 float TempY; 5562 float oldZ; 5563 5564 if (l == line || !l->backsector) 5565 { 5566 continue; 5567 } 5568 5569 // Get the thing's position along the source linedef 5570 SrcXAxis = Normalise(*line->v2 - *line->v1); 5571 SrcYAxis = -line->normal; 5572 pos = DotProduct(SrcXAxis, thing.Origin - *line->v1); 5573 oldZ = thing.Origin.z; 5574 5575 // Interpolate position across the exit linedef 5576 if (reverse) 5577 { 5578 DstXAxis = Normalise(*l->v2 - *l->v1); 5579 DstYAxis = -l->normal; 5580 newPos = *l->v1 + pos * DstXAxis; 5581 newPos.z = thing.Origin.z - GetPlanePointZ( 5582 line->frontsector->botregion->floor, thing.Origin) + 5583 GetPlanePointZ(l->frontsector->botregion->floor, newPos); 5584 } 5585 else 5586 { 5587 DstXAxis = Normalise(*l->v1 - *l->v2); 5588 DstYAxis = l->normal; 5589 newPos = *l->v2 + pos * DstXAxis; 5590 newPos.z = thing.Origin.z - GetPlanePointZ( 5591 line->frontsector->botregion->floor, thing.Origin) + 5592 GetPlanePointZ(l->backsector->botregion->floor, newPos); 5593 } 5594 5595 // Attempt to teleport, aborting if blocked 5596 if (!thing.TeleportMove(newPos)) 5597 { 5598 return false; 5599 } 5600 5601 // Rotate thing's orientation according to difference in linedef angles 5602 TempV.x = DotProduct(DstXAxis, SrcXAxis); 5603 TempV.y = DotProduct(DstYAxis, SrcXAxis); 5604 TempV.z = 0.0; 5605 VectorAngles(&TempV, &TempA); 5606 thing.Angles.yaw = AngleMod360(thing.Angles.yaw - TempA.yaw); 5607 5608 // Rotate thing's momentum to come out of exit just like it entered 5609 TempX = DotProduct(thing.Velocity, SrcXAxis); 5610 TempY = DotProduct(thing.Velocity, SrcYAxis); 5611 thing.Velocity.x = TempX * DstXAxis.x + TempY * DstYAxis.x; 5612 thing.Velocity.y = TempX * DstXAxis.y + TempY * DstYAxis.y; 5613 5614 // Adjust a player's view, in case there has been a height change 5615 if (thing.bIsPlayer && thing.Player.MO == thing) 5616 { 5617 thing.Player.ViewOrg.z += thing.Origin.z - oldZ; 5618 5619 thing.Player.bFixAngle = true; 5620 } 5621 return true; 5622 } 5623 return false; 5624} 5625 5626//************************************************************************** 5627// 5628// Noise alert 5629// 5630//************************************************************************** 5631 5632//========================================================================== 5633// 5634// RecursiveSound 5635// 5636// Called by NoiseAlert. Recursively traverse adjacent sectors, sound 5637// blocking lines cut off traversal. 5638// 5639//========================================================================== 5640 5641final void RecursiveSound(sector_t* sec, int soundblocks, Entity soundtarget, 5642 bool Splash) 5643{ 5644 int i; 5645 line_t* check; 5646 sector_t* other; 5647 Entity Ent; 5648 5649 // wake up all monsters in this sector 5650 if (sec->validcount == *Game.validcount && 5651 sec->soundtraversed <= soundblocks + 1) 5652 { 5653 return; // already flooded 5654 } 5655 5656 sec->validcount = *Game.validcount; 5657 sec->soundtraversed = soundblocks + 1; 5658 sec->SoundTarget = soundtarget; 5659 5660 for (Ent = sec->ThingList; Ent; Ent = Ent.SNext) 5661 { 5662 if (Ent != soundtarget && (!Splash || !EntityEx(Ent).bNoSplashAlert)) 5663 { 5664 EntityEx(Ent).LastHeard = EntityEx(soundtarget); 5665 } 5666 } 5667 5668 for (i = 0; i < sec->linecount; i++) 5669 { 5670 check = sec->lines[i]; 5671 if (check->sidenum[1] == -1 || !(check->flags & ML_TWOSIDED)) 5672 { 5673 continue; 5674 } 5675 5676 // Early out for intra-sector lines 5677 if (check->frontsector == check->backsector) 5678 { 5679 continue; 5680 } 5681 5682 if (!LineOpenings(check, *check->v1)) 5683 { 5684 if (!LineOpenings(check, *check->v2)) 5685 { 5686 // Closed door 5687 continue; 5688 } 5689 } 5690 5691 if (check->frontsector == sec) 5692 { 5693 other = check->backsector; 5694 } 5695 else 5696 { 5697 other = check->frontsector; 5698 } 5699 5700 if (check->flags & ML_SOUNDBLOCK) 5701 { 5702 if (!soundblocks) 5703 { 5704 RecursiveSound(other, 1, soundtarget, Splash); 5705 } 5706 } 5707 else 5708 { 5709 RecursiveSound(other, soundblocks, soundtarget, Splash); 5710 } 5711 } 5712} 5713 5714//========================================================================== 5715// 5716// NoiseAlert 5717// 5718// If a monster yells at a player, it will alert other monsters to the 5719// player. 5720// 5721//========================================================================== 5722 5723final void NoiseAlert(Entity target, Entity emmiter, optional bool Splash) 5724{ 5725 if (!emmiter) 5726 { 5727 return; 5728 } 5729 5730 if (target && target.bIsPlayer && PlayerEx(target.Player).bNoTarget) 5731 { 5732 return; 5733 } 5734 5735 if (!specified_Splash) 5736 { 5737 Splash = false; 5738 } 5739 5740 (*Game.validcount)++; 5741 RecursiveSound(emmiter.Sector, 0, target, Splash); 5742} 5743 5744//========================================================================== 5745// 5746// EV_NoiseAlert 5747// 5748//========================================================================== 5749 5750final bool EV_NoiseAlert(EntityEx A, int Arg1, int Arg2, int Arg3, int Arg4, 5751 int Arg5) 5752{ 5753 EntityEx ASource; 5754 EntityEx ATarget; 5755 5756 if (!Arg1) 5757 { 5758 ASource = A; 5759 } 5760 else 5761 { 5762 ASource = EntityEx(FindMobjFromTID(Arg1, none)); 5763 } 5764 5765 if (!Arg2) 5766 { 5767 ATarget = A; 5768 } 5769 else 5770 { 5771 ATarget = EntityEx(FindMobjFromTID(Arg2, none)); 5772 } 5773 5774 NoiseAlert(ASource, ATarget); 5775 return true; 5776} 5777 5778//========================================================================== 5779// 5780// EV_LineSearchForPuzzleItem 5781// 5782//========================================================================== 5783 5784final bool EV_LineSearchForPuzzleItem(int Arg1, int Arg2, int Arg3, int Arg4, 5785 int Arg5, EntityEx A) 5786{ 5787 Inventory Item; 5788 5789 if (!A) 5790 return false; 5791 if (!A.bIsPlayer) 5792 return false; 5793 5794 // Search player's inventory for puzzle items 5795 for (Item = A.Inventory; Item; Item = Item.Inventory) 5796 { 5797 if (!PuzzleItem(Item)) 5798 continue; 5799 if (PuzzleItem(Item).PuzzleItemNumber == Arg1) 5800 { 5801 // A puzzle item was found for the line 5802 if (A.UseInventory(Item)) 5803 { 5804 return true; 5805 } 5806 } 5807 } 5808 return false; 5809} 5810 5811//************************************************************************** 5812// 5813// Point pushers and pullers 5814// 5815//************************************************************************** 5816 5817//========================================================================== 5818// 5819// GetPushThing 5820// 5821// Returns a pointer to an MT_PUSH or MT_PULL thing, NULL otherwise. 5822// 5823//========================================================================== 5824 5825final EntityEx GetPushThing(int s) 5826{ 5827 Entity thing; 5828 sector_t* sec; 5829 5830 sec = &XLevel.Sectors[s]; 5831 for (thing = sec->ThingList; thing; thing = thing.SNext) 5832 { 5833 if (PointPusher(thing) || PointPuller(thing)) 5834 { 5835 return EntityEx(thing); 5836 } 5837 } 5838 return none; 5839} 5840 5841//========================================================================== 5842// 5843// SpawnPushers 5844// 5845// Initialise the sectors where pushers are present 5846// 5847//========================================================================== 5848 5849final void SpawnPushers() 5850{ 5851 int i; 5852 line_t* l; 5853 int s; 5854 Pusher P; 5855 EntityEx thing; 5856 5857 for (i = 0; i < XLevel.NumLines; i++) 5858 { 5859 l = &XLevel.Lines[i]; 5860 switch (l->special) 5861 { 5862 case LNSPEC_SectorSetWind: // wind 5863 for (s = XLevel.FindSectorFromTag(l->arg1, -1); s >= 0; 5864 s = XLevel.FindSectorFromTag(l->arg1, s)) 5865 { 5866 P = Spawn(Pusher); 5867 P.Init(Pusher::PUSHER_Wind, l->arg4 ? l : NULL, l->arg2, 5868 l->arg3, none, s); 5869 } 5870 break; 5871 5872 case LNSPEC_SectorSetCurrent: // current 5873 for (s = XLevel.FindSectorFromTag(l->arg1, -1); s >= 0; 5874 s = XLevel.FindSectorFromTag(l->arg1, s)) 5875 { 5876 P = Spawn(Pusher); 5877 P.Init(Pusher::PUSHER_Current, l->arg4 ? l : NULL, l->arg2, 5878 l->arg3, none, s); 5879 } 5880 break; 5881 5882 case LNSPEC_PointPushSetForce: // push/pull 5883 if (l->arg1) 5884 { 5885 // [RH] Find thing by sector 5886 for (s = XLevel.FindSectorFromTag(l->arg1, -1); s >= 0; 5887 s = XLevel.FindSectorFromTag(l->arg1, s)) 5888 { 5889 thing = GetPushThing(s); 5890 if (thing) 5891 { 5892 // No MT_P* means no effect 5893 // [RH] Allow narrowing it down by tid 5894 if (!l->arg2 || l->arg2 == thing.TID) 5895 { 5896 P = Spawn(Pusher); 5897 P.Init(Pusher::PUSHER_Push, l->arg4 ? l : NULL, 5898 l->arg3, 0, thing, s); 5899 } 5900 } 5901 } 5902 } 5903 else 5904 { 5905 // [RH] Find thing by tid 5906 for (thing = EntityEx(FindMobjFromTID(l->arg2, none)); 5907 thing; 5908 thing = EntityEx(FindMobjFromTID(l->arg2, thing))) 5909 { 5910 if (PointPuller(thing) || PointPusher(thing)) 5911 { 5912 for (s = 0; s < XLevel.NumSectors; s++) 5913 if (&XLevel.Sectors[s] == thing.Sector) 5914 break; 5915 P = Spawn(Pusher); 5916 P.Init(Pusher::PUSHER_Push, l->arg4 ? l : NULL, 5917 l->arg3, 0, thing, s); 5918 } 5919 } 5920 } 5921 break; 5922 } 5923 } 5924} 5925 5926//========================================================================== 5927// 5928// AdjustPusher 5929// 5930//========================================================================== 5931 5932final bool AdjustPusher(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 5933 line_t* Line, int Type) 5934{ 5935 int tag; 5936 int magnitude; 5937 int angle; 5938 Pusher Collection; 5939 Pusher P; 5940 int secnum; 5941 5942 if (Line || Arg4) 5943 { 5944 return false; 5945 } 5946 tag = Arg1; 5947 magnitude = Arg2; 5948 angle = Arg3; 5949 Collection = none; 5950 // Find pushers already attached to the sector, and change their parameters. 5951 foreach AllThinkers(Pusher, P) 5952 { 5953 if (P.CheckForSectorMatch(Type, tag) >= 0) 5954 { 5955 P.ChangeValues(magnitude, angle); 5956 P.AdjustLink = Collection; 5957 Collection = P; 5958 } 5959 } 5960 5961 // Now create pushers for any sectors that don't already have them. 5962 for (secnum = XLevel.FindSectorFromTag(tag, -1); secnum >= 0; 5963 secnum = XLevel.FindSectorFromTag(tag, secnum)) 5964 { 5965 for (P = Collection; P; P = P.AdjustLink) 5966 { 5967 if (P.Affectee == secnum) 5968 break; 5969 } 5970 if (!P) 5971 { 5972 P = Spawn(Pusher); 5973 P.Init(Type, NULL, magnitude, angle, none, secnum); 5974 } 5975 } 5976 return true; 5977} 5978 5979//========================================================================== 5980// 5981// EV_LineTranslucent 5982// 5983//========================================================================== 5984 5985final bool EV_LineTranslucent(int Arg1, int Arg2, int Arg3, int Arg4, 5986 int Arg5) 5987{ 5988 int Searcher; 5989 line_t* Other; 5990 5991 Searcher = -1; 5992 for (Other = XLevel.FindLine(Arg1, &Searcher); Other; 5993 Other = XLevel.FindLine(Arg1, &Searcher)) 5994 { 5995 Other->alpha = itof(Arg2) / 255.0; 5996 if (Arg3 == 0) 5997 { 5998 Other->flags &= ~ML_ADDITIVE; 5999 } 6000 else if (Arg3 == 1) 6001 { 6002 Other->flags |= ML_ADDITIVE; 6003 } 6004 else 6005 { 6006 print("Invalid line translucency type %d", Arg3); 6007 } 6008 } 6009 return true; 6010} 6011 6012//========================================================================== 6013// 6014// EV_SectorSetPlaneReflection 6015// 6016//========================================================================== 6017 6018int EV_SectorSetPlaneReflection(int Arg1, int Arg2, int Arg3, int Arg4, 6019 int Arg5) 6020{ 6021 int SecNum; 6022 6023 for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0; 6024 SecNum = XLevel.FindSectorFromTag(Arg1, SecNum)) 6025 { 6026 XLevel.Sectors[SecNum].floor.MirrorAlpha = itof(255 - Arg2) / 255.0; 6027 XLevel.Sectors[SecNum].ceiling.MirrorAlpha = itof(255 - Arg3) / 255.0; 6028 } 6029 return 1; 6030} 6031 6032//========================================================================== 6033// 6034// EV_GlassBreak 6035// 6036//========================================================================== 6037 6038final bool EV_GlassBreak(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5, 6039 line_t* Line, EntityEx A) 6040{ 6041 bool switched; 6042 byte Quest1; 6043 byte Quest2; 6044 float x; 6045 float y; 6046 int i; 6047 EntityEx glass; 6048 float an; 6049 float speed; 6050 6051 if (!Line) 6052 { 6053 return false; 6054 } 6055 if (Line->flags & ML_TWOSIDED) 6056 { 6057 Line->flags &= ~(ML_BLOCKING | ML_BLOCKEVERYTHING); 6058 } 6059 switched = ChangeSwitchTexture(Line->sidenum[0], false, 6060 'switches/normbutn', Quest1); 6061 Line->special = 0; 6062 if (Line->sidenum[1] != -1) 6063 { 6064 if (ChangeSwitchTexture(Line->sidenum[1], false, 'switches/normbutn', 6065 Quest2)) 6066 { 6067 switched = true; 6068 } 6069 } 6070 else 6071 { 6072 Quest2 = Quest1; 6073 } 6074 if (switched) 6075 { 6076 if (!Arg1) 6077 { 6078 // Break some glass 6079 x = Line->v1->x + (Line->v2->x - Line->v1->x) / 2.0; 6080 y = Line->v1->y + (Line->v2->y - Line->v1->y) / 2.0; 6081 x += (Line->frontsector->soundorg.x - x) / 5.0; 6082 y += (Line->frontsector->soundorg.y - y) / 5.0; 6083 6084 for (i = 0; i < 7; i++) 6085 { 6086 glass = Spawn(GlassJunk, vector(x, y, EntityEx::ONFLOORZ)); 6087 glass.Origin.z += 24.0; 6088 glass.SetState(GetStatePlus(glass.IdleState, P_Random() % glass.Health, true)); 6089 an = Random() * 360.0; 6090 glass.Angles.yaw = an; 6091 speed = Random() * 4.0 * 35.0; 6092 glass.Velocity.x = cos(an) * speed; 6093 glass.Velocity.y = sin(an) * speed; 6094 glass.Velocity.z = Random() * 8.0 * 35.0; 6095 } 6096 } 6097 if (Quest1 || Quest2) 6098 { 6099 // Up stats and signal this mission is complete 6100 if (!A) 6101 { 6102 for (i = 0; i < MAXPLAYERS; ++i) 6103 { 6104 if (Game.Players[i] && Game.Players[i].bSpawned) 6105 { 6106 A = EntityEx(Game.Players[i].MO); 6107 break; 6108 } 6109 } 6110 } 6111 if (A && A.bIsPlayer) 6112 { 6113 A.GiveInventoryType(QuestItem29); 6114 A.GiveInventoryType(class<Inventory>(FindClass('UpgradeAccuracy'))); 6115 A.GiveInventoryType(class<Inventory>(FindClass('UpgradeStamina'))); 6116 } 6117 } 6118 } 6119 // We already changed the switch texture, so don't make the main code 6120 // switch it back. 6121 return false; 6122} 6123 6124//========================================================================== 6125// 6126// EV_SendToCommunicator 6127// 6128//========================================================================== 6129 6130final bool EV_SendToCommunicator(EntityEx A, int Arg1, int Arg2, int Arg3, 6131 int Arg4, int Arg5, int Side) 6132{ 6133 if (Arg2 && Side) 6134 { 6135 return false; 6136 } 6137 6138 if (A && A.bIsPlayer && A.FindInventory(Communicator)) 6139 { 6140 if (!Arg4) 6141 { 6142 PlayerEx(A.Player).SetObjectives(Arg1); 6143 } 6144 6145 PlayerEx(A.Player).ClientVoice(Arg1); 6146 6147 if (Arg3 == 0) 6148 { 6149 A.Player.cprint("Incoming Message"); 6150 } 6151 else if (Arg3 == 1) 6152 { 6153 A.Player.cprint("Incoming Message from BlackBird"); 6154 } 6155 return true; 6156 } 6157 return false; 6158} 6159 6160//========================================================================== 6161// 6162// EV_StartConversation 6163// 6164//========================================================================== 6165 6166final bool EV_StartConversation(int Arg1, int Arg2, int Arg3, int Arg4, 6167 int Arg5, EntityEx A) 6168{ 6169 EntityEx Ent; 6170 6171 if (!A || !A.bIsPlayer) 6172 { 6173 return false; 6174 } 6175 for (Ent = EntityEx(FindMobjFromTID(Arg1, none)); Ent; 6176 Ent = EntityEx(FindMobjFromTID(Arg1, Ent))) 6177 { 6178 if (StartConversation(A, Ent)) 6179 { 6180 if (Arg2) 6181 { 6182 A.Angles.yaw = atan2(Ent.Origin.y - A.Origin.y, 6183 Ent.Origin.x - A.Origin.x); 6184 A.Player.bFixAngle = true; 6185 } 6186 return true; 6187 } 6188 } 6189 return false; 6190} 6191 6192//=========================================================================== 6193// Quake variables 6194// 6195// Arg1 Intensity on richter scale (2..9) 6196// Arg2 Duration in tics 6197// Arg3 Radius for damage, in tile units (64 pixels) 6198// Arg4 Radius for tremor in tile units (64 pixels) 6199// Arg5 TID of map thing for focus of quake 6200// 6201//=========================================================================== 6202 6203//=========================================================================== 6204// 6205// A_LocalQuake 6206// 6207//=========================================================================== 6208 6209final bool A_LocalQuake(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5) 6210{ 6211 QuakeFocus focus; 6212 EntityEx target; 6213 int success = false; 6214 6215 // Find all quake foci 6216 for (target = EntityEx(FindMobjFromTID(Arg5, none)); target; 6217 target = EntityEx(FindMobjFromTID(Arg5, target))) 6218 { 6219 focus = Spawn(QuakeFocus, target.Origin); 6220 if (focus) 6221 { 6222 focus.Richters = Arg1; 6223 focus.QuakeDuration = Arg2 >> 1; // decremented every 2 tics 6224 focus.DamageRadius = itof(Arg3) * 64.0; 6225 focus.TremorRadius = itof(Arg4) * 64.0; 6226 success = true; 6227 } 6228 } 6229 6230 return success; 6231} 6232 6233//========================================================================== 6234// 6235// ForceLightning 6236// 6237//========================================================================== 6238 6239final void ForceLightning(int Mode) 6240{ 6241 LightningThinker Lightning = none; 6242 Thinker Th; 6243 foreach AllThinkers(LightningThinker, Th) 6244 { 6245 Lightning = LightningThinker(Th); 6246 break; 6247 } 6248 if (!Lightning) 6249 { 6250 Lightning = Spawn(LightningThinker); 6251 Lightning.Init(); 6252 } 6253 Lightning.ForceLightning(Mode); 6254} 6255 6256//========================================================================== 6257// 6258// ModToDamageType 6259// 6260//========================================================================== 6261 6262final name ModToDamageType(int Mod) 6263{ 6264 switch (Mod) 6265 { 6266 case 9: 6267 return 'BFGSplash'; 6268 case 12: 6269 return 'Drowning'; 6270 case 13: 6271 return 'Slime'; 6272 case 14: 6273 return 'Fire'; 6274 case 15: 6275 return 'Crush'; 6276 case 16: 6277 return 'Telefrag'; 6278 case 17: 6279 return 'Falling'; 6280 case 18: 6281 return 'Suicide'; 6282 case 20: 6283 return 'Exit'; 6284 case 22: 6285 return 'Melee'; 6286 case 23: 6287 return 'Railgun'; 6288 case 24: 6289 return 'Ice'; 6290 case 25: 6291 return 'Disintegrate'; 6292 case 26: 6293 return 'Poison'; 6294 case 27: 6295 return 'Electric'; 6296 case 1000: 6297 return 'Massacre'; 6298 default: 6299 return ''; 6300 } 6301} 6302 6303//========================================================================== 6304// 6305// PolyThrustMobj 6306// 6307//========================================================================== 6308 6309final void PolyThrustMobj(Entity Other, TVec thrustDir, polyobj_t* po) 6310{ 6311 float force; 6312 PolyobjThinker pe; 6313 6314 if (!EntityEx(Other).bShootable && !Other.bIsPlayer) 6315 { 6316 return; 6317 } 6318 6319 pe = PolyobjThinker(po->SpecialData); 6320 if (pe) 6321 { 6322 force = pe.thrust_force; 6323 if (force < 1.0) 6324 { 6325 force = 1.0; 6326 } 6327 else if (force > 128.0) 6328 { 6329 force = 128.0; 6330 } 6331 } 6332 else 6333 { 6334 force = 1.0; 6335 } 6336 6337 Other.Velocity += force * thrustDir; 6338 if (po->bCrush) 6339 { 6340 TVec testPos; 6341 6342 testPos = Other.Origin + force * thrustDir * Game.frametime; 6343 if (po->bHurtOnTouch || !Other.CheckPosition(testPos)) 6344 { 6345 EntityEx(Other).Damage(none, none, 3, 'Crush'); 6346 } 6347 } 6348} 6349 6350//========================================================================== 6351// 6352// PolyBusy 6353// 6354//========================================================================== 6355 6356final bool PolyBusy(int polyobj) 6357{ 6358 polyobj_t* poly = XLevel.GetPolyobj(polyobj); 6359 if (!poly || !poly->SpecialData) 6360 { 6361 return false; 6362 } 6363 else 6364 { 6365 return true; 6366 } 6367} 6368 6369//========================================================================== 6370// 6371// ThingCount 6372// 6373//========================================================================== 6374 6375final int ThingCount(int type, name TypeName, int tid, int SectorTag) 6376{ 6377 class<EntityEx> moType; 6378 6379 if (!(type + tid) && !TypeName) 6380 { 6381 // Nothing to count 6382 return 0; 6383 } 6384 if (TypeName) 6385 { 6386 moType = class<EntityEx>(FindClassLowerCase(TypeName)); 6387 if (!moType) 6388 { 6389 return 0; 6390 } 6391 } 6392 else 6393 { 6394 moType = class<EntityEx>(FindClassFromScriptId(type, 6395 LineSpecialGameInfo(Game).GameFilterFlag)); 6396 } 6397 6398 // Count things. 6399 int Count = DoThingCount(moType, tid, SectorTag); 6400 if (moType) 6401 { 6402 // If this class has a replacement, count number of instances of 6403 // replacement class too. 6404 class<EntityEx> RepType = class<EntityEx>(GetClassReplacement( 6405 moType)); 6406 if (RepType != moType) 6407 { 6408 Count += DoThingCount(RepType, tid, SectorTag); 6409 } 6410 } 6411 return Count; 6412} 6413 6414//========================================================================== 6415// 6416// DoThingCount 6417// 6418//========================================================================== 6419 6420final int DoThingCount(class<EntityEx> moType, int tid, int SectorTag) 6421{ 6422 int count; 6423 EntityEx Ent; 6424 6425 count = 0; 6426 if (tid) 6427 { 6428 // Count TID things 6429 for (Ent = EntityEx(FindMobjFromTID(tid, none)); Ent != none; 6430 Ent = EntityEx(FindMobjFromTID(tid, Ent))) 6431 { 6432 if (Ent.bMonster && Ent.Health <= 0) 6433 { 6434 // Don't count dead monsters 6435 continue; 6436 } 6437 if (Inventory(Ent) && Ent.Owner) 6438 { 6439 // Don't count things in somebody's inventory 6440 continue; 6441 } 6442 if (SectorTag != -1 && Ent.Sector->tag != SectorTag) 6443 { 6444 // Wrong sector tag. 6445 continue; 6446 } 6447 if (!moType || moType == Ent.Class) 6448 { 6449 count++; 6450 } 6451 } 6452 } 6453 else if (moType) 6454 { 6455 // Count only types 6456 foreach AllThinkers(moType, Ent) 6457 { 6458 if (Ent.Class != moType) 6459 { 6460 // Doesn't match 6461 continue; 6462 } 6463 if (Ent.bMonster && Ent.Health <= 0) 6464 { 6465 // Don't count dead monsters 6466 continue; 6467 } 6468 if (Inventory(Ent) && Ent.Owner) 6469 { 6470 // Don't count things in somebody's inventory 6471 continue; 6472 } 6473 if (SectorTag != -1 && Ent.Sector->tag != SectorTag) 6474 { 6475 // Wrong sector tag. 6476 continue; 6477 } 6478 count++; 6479 } 6480 } 6481 return count; 6482} 6483 6484//========================================================================== 6485// 6486// SectorDamage 6487// 6488//========================================================================== 6489 6490final void SectorDamage(int Tag, int Amount, name DamageType, 6491 name ProtectionType, int Flags) 6492{ 6493 class<Inventory> ProtectClass = class<Inventory>(FindClassLowerCase( 6494 ProtectionType)); 6495 6496 int SecNum = -1; 6497 for (SecNum = XLevel.FindSectorFromTag(Tag, -1); SecNum >= 0; 6498 SecNum = XLevel.FindSectorFromTag(Tag, SecNum)) 6499 { 6500 sector_t* Sec = &XLevel.Sectors[SecNum]; 6501 Entity Ent; 6502 Entity Next; 6503 6504 for (Ent = Sec->ThingList; Ent; Ent = Next) 6505 { 6506 Next = Ent.SNext; 6507 6508 if (!EntityEx(Ent).bShootable) 6509 { 6510 continue; 6511 } 6512 if (!(Flags & DAMAGE_NONPLAYERS) && !Ent.bIsPlayer) 6513 { 6514 continue; 6515 } 6516 if (!(Flags & DAMAGE_PLAYERS) && Ent.bIsPlayer) 6517 { 6518 continue; 6519 } 6520 6521 if (!(Flags & DAMAGE_IN_AIR) && Ent.Origin.z != 6522 GetPlanePointZ(&Sec->floor, Ent.Origin) && !Ent.WaterLevel) 6523 { 6524 continue; 6525 } 6526 6527 if (ProtectClass) 6528 { 6529 if (!(Flags & DAMAGE_SUBCLASSES_PROTECT)) 6530 { 6531 if (EntityEx(Ent).FindInventory(ProtectClass)) 6532 { 6533 continue; 6534 } 6535 } 6536 else 6537 { 6538 Inventory Item; 6539 6540 for (Item = EntityEx(Ent).Inventory; Item; Item = Item.Inventory) 6541 { 6542 if (Item.IsA(ProtectionType)) 6543 { 6544 break; 6545 } 6546 } 6547 if (Item) 6548 { 6549 continue; 6550 } 6551 } 6552 } 6553 6554 EntityEx(Ent).Damage(none, none, Amount, DamageType); 6555 } 6556 } 6557} 6558 6559//========================================================================== 6560// 6561// SpawnMapThing 6562// 6563// The fields of the mapthing should already be in host byte order. 6564// 6565//========================================================================== 6566 6567final void SpawnMapThing(mthing_t* mthing) 6568{ 6569 int spawnMask; 6570 class<EntityEx> moClass; 6571 6572 if (mthing->type <= 0) 6573 { 6574 return; 6575 } 6576 6577 // count deathmatch start positions 6578 if (mthing->type == 11) 6579 { 6580 DeathmatchStarts.Num = DeathmatchStarts.Num + 1; 6581 CopyMThing(mthing, &DeathmatchStarts[DeathmatchStarts.Num - 1]); 6582 return; 6583 } 6584 6585 if (bCheckStrifeStartSpots && mthing->type >= 118 && mthing->type <= 127) 6586 { 6587 // Map start spots, i.e. player starts. 6588 mthing->arg1 = mthing->type - 117; 6589 mthing->type = 1; 6590 } 6591 6592 // Check for player starts 1 to 4 6593 bool IsPlayerStart = false; 6594 if (mthing->type <= 4) 6595 { 6596 IsPlayerStart = true; 6597 } 6598 // Check for player starts 5 to 8 6599 if (mthing->type >= ExtPlayersBase && mthing->type < ExtPlayersBase + 4) 6600 { 6601 // Change type to range 5-8. 6602 mthing->type = 5 + mthing->type - ExtPlayersBase; 6603 IsPlayerStart = true; 6604 } 6605 6606 if (!IsPlayerStart || bFilterStarts) 6607 { 6608 // Check current game type with spawn flags 6609 if (Game.netgame == false) 6610 { 6611 spawnMask = MTF_GSINGLE; 6612 } 6613 else if (Game.deathmatch) 6614 { 6615 spawnMask = MTF_GDEATHMATCH; 6616 } 6617 else 6618 { 6619 spawnMask = MTF_GCOOP; 6620 } 6621 if (!(mthing->options & spawnMask)) 6622 { 6623 return; 6624 } 6625 6626 // Check current skill with spawn flags 6627 if (!(mthing->SkillClassFilter & World.SkillSpawnFilter)) 6628 { 6629 return; 6630 } 6631 6632 // Check current character classes with spawn flags 6633 spawnMask = GetPClassSpawnFlags(); 6634 if (spawnMask && !(mthing->SkillClassFilter & spawnMask)) 6635 { 6636 // Not for current class 6637 return; 6638 } 6639 } 6640 6641 if (IsPlayerStart) 6642 { 6643 // save spots for respawning in network games 6644 PlayerStarts.Num = PlayerStarts.Num + 1; 6645 CopyMThing(mthing, &PlayerStarts[PlayerStarts.Num - 1]); 6646 return; 6647 } 6648 6649 // Sector's sound sequence types. 6650 if (mthing->type >= 1400 && mthing->type < 1410) 6651 { 6652 XLevel.PointInSector(vector(mthing->x, 6653 mthing->y, 0.0))->seqType = mthing->type - 1400; 6654 return; 6655 } 6656 if (mthing->type == 1411) 6657 { 6658 int SeqType; 6659 6660 if (mthing->arg1 == 255) 6661 SeqType = -1; 6662 else 6663 SeqType = mthing->arg1; 6664 6665 if (SeqType > 63) 6666 { 6667 print("Sound sequence %d out of range", SeqType); 6668 } 6669 else 6670 { 6671 XLevel.PointInSector(vector(mthing->x, 6672 mthing->y, 0.0))->seqType = SeqType; 6673 } 6674 return; 6675 } 6676 6677 // Remap old ambient sound types to the generic one. 6678 if (mthing->type >= 14001 && mthing->type <= 14064) 6679 { 6680 mthing->arg1 = mthing->type - 14000; 6681 mthing->type = 14065; 6682 } 6683 6684 // find which type to spawn 6685 moClass = class<EntityEx>(FindClassFromEditorId(mthing->type, 6686 LineSpecialGameInfo(Game).GameFilterFlag)); 6687 if (!moClass) 6688 { 6689 // Can't find thing type 6690 dprint("SpawnMapThing: Unknown type %d at (%f, %f)", 6691 mthing->type, mthing->x, mthing->y); 6692 moClass = Unknown; 6693 } 6694 else 6695 { 6696 moClass = class<EntityEx>(GetClassReplacement(moClass)); 6697 6698 // If actor has no sprites, also map it to the unknown thing 6699 if (FindClassState(moClass, 'Spawn') && 6700 !AreStateSpritesPresent(FindClassState(moClass, 'Spawn'))) 6701 { 6702 print("%n at (%f, %f), has no frames", GetClassName(moClass), 6703 mthing->x, mthing->y); 6704 moClass = Unknown; 6705 } 6706 } 6707 6708 if (Level.Game.nomonsters && moClass.default.bMonster) 6709 { 6710 return; 6711 } 6712 6713 // spawn it 6714 Spawn(moClass,,, mthing, false); 6715} 6716 6717//========================================================================== 6718// 6719// CheckLock 6720// 6721//========================================================================== 6722 6723final bool CheckLock(Entity user, int lock, bool door) 6724{ 6725 if (!user.bIsPlayer) 6726 { 6727 return false; 6728 } 6729 if (!lock) 6730 { 6731 return true; 6732 } 6733 LockDef* Lock = GetLockDef(lock); 6734 if (!Lock) 6735 { 6736 if (lock == 103) 6737 { 6738 user.Player.centreprint(Lock103Message); 6739 } 6740 else 6741 { 6742 user.Player.centreprint("That doesn't seem to work"); 6743 } 6744 user.PlaySound('misc/keytry', CHAN_VOICE); 6745 return false; 6746 } 6747 if (CheckLockDef(Lock, EntityEx(user))) 6748 { 6749 return true; 6750 } 6751 user.Player.centreprint(door ? Lock->Message : Lock->RemoteMessage); 6752 user.PlaySound(Lock->LockedSound, CHAN_VOICE); 6753 return false; 6754} 6755 6756//========================================================================== 6757// 6758// GetDefaultDoorSound 6759// 6760//========================================================================== 6761 6762final bool CheckLockDef(LockDef* Lock, EntityEx User) 6763{ 6764 int i; 6765 int j; 6766 Inventory Item; 6767 6768 // Empty lock list means check for any key. 6769 if (!Lock->Locks.Num) 6770 { 6771 for (Item = User.Inventory; Item; Item = Item.Inventory) 6772 { 6773 if (Key(Item)) 6774 { 6775 return true; 6776 } 6777 } 6778 return false; 6779 } 6780 6781 for (i = 0; i < Lock->Locks.Num; i++) 6782 { 6783 bool Good = false; 6784 for (j = 0; j < Lock->Locks[i].AnyKeyList.Num; j++) 6785 { 6786 if (User.FindInventory(class<Inventory>( 6787 Lock->Locks[i].AnyKeyList[j]))) 6788 { 6789 Good = true; 6790 break; 6791 } 6792 } 6793 if (!Good) 6794 { 6795 return false; 6796 } 6797 } 6798 return true; 6799} 6800 6801//************************************************************************** 6802// 6803// CONVERSATION STUFF 6804// 6805//************************************************************************** 6806 6807//========================================================================== 6808// 6809// StartConversation 6810// 6811//========================================================================== 6812 6813bool StartConversation(EntityEx User, EntityEx UseOn) 6814{ 6815 int SpeechNum; 6816 6817 if (Game.netgame) 6818 { 6819 return false; 6820 } 6821 if (!User || !User.bIsPlayer || User.Health <= 0) 6822 { 6823 return false; 6824 } 6825 if (!UseOn || UseOn.Health <= 0) 6826 { 6827 return false; 6828 } 6829 if (UseOn.bInCombat) 6830 { 6831 // This dude is too busy to talk right now. 6832 return false; 6833 } 6834 if (!UseOn.ConversationID) 6835 { 6836 return false; 6837 } 6838 SpeechNum = UseOn.GetSpeech(); 6839 if (SpeechNum) 6840 { 6841 CurrentSpeaker = UseOn; 6842 CurrentSpeakingTo = User; 6843 OldSpeakerAngle = UseOn.Angles.yaw; 6844 UseOn.Angles.yaw = atan2(User.Origin.y - UseOn.Origin.y, 6845 User.Origin.x - UseOn.Origin.x); 6846 User.PlaySound('misc/chat', CHAN_VOICE); 6847 StartSpeech(SpeechNum); 6848 } 6849 return true; 6850} 6851 6852//========================================================================== 6853// 6854// StartSpeech 6855// 6856//========================================================================== 6857 6858final void StartSpeech(int SpeechNum) 6859{ 6860 RogueConSpeech *Speech; 6861 bool conJumped; 6862 6863 do 6864 { 6865 conJumped = false; 6866 if (!SpeechNum) 6867 { 6868 StopSpeech(); 6869 return; 6870 } 6871 if (SpeechNum < 0) 6872 { 6873 Speech = &XLevel.GenericSpeeches[-SpeechNum - 1]; 6874 } 6875 else 6876 { 6877 Speech = &XLevel.LevelSpeeches[SpeechNum - 1]; 6878 } 6879 if (Speech->JumpToConv && 6880 CheckForNeededItem(CurrentSpeakingTo, Speech->CheckItem1, 1) != -2 && 6881 CheckForNeededItem(CurrentSpeakingTo, Speech->CheckItem2, 1) != -2 && 6882 CheckForNeededItem(CurrentSpeakingTo, Speech->CheckItem3, 1) != -2) 6883 { 6884 CurrentSpeaker.CurrentSpeech = Speech->JumpToConv; 6885 SpeechNum = CurrentSpeaker.GetSpeech(); 6886 conJumped = true; 6887 } 6888 } 6889 while (conJumped); 6890 CurrentSpeechIndex = SpeechNum; 6891 6892 PlayerEx(CurrentSpeakingTo.Player).ClientSpeech(CurrentSpeaker, SpeechNum); 6893} 6894 6895//========================================================================== 6896// 6897// GetClassFromID 6898// 6899//========================================================================== 6900 6901final class<EntityEx> GetClassFromID(int ID) 6902{ 6903 class Cls; 6904 if (ID) 6905 { 6906 foreach (AllClasses(EntityEx, Cls)) 6907 { 6908 if (class<EntityEx>(Cls).default.ConversationID == ID) 6909 { 6910 return class<EntityEx>(Cls); 6911 } 6912 } 6913 print("Unknown item %d", ID); 6914 } 6915 6916 return none; 6917} 6918 6919//========================================================================== 6920// 6921// CheckForNeededItem 6922// 6923//========================================================================== 6924 6925final int CheckForNeededItem(EntityEx A, int ID, int Amount) 6926{ 6927 int i; 6928 6929 if (!ID) 6930 { 6931 return -1; 6932 } 6933 // Get class ID. 6934 class<EntityEx> CID = GetClassFromID(ID); 6935 // Check inventory items. 6936 if (class<Inventory>(CID) && class<Inventory>(CID).default.bInvBar) 6937 { 6938 Inventory Item = A.FindInventory(class<Inventory>(CID)); 6939 return !Item || Item.Amount < Amount ? -2 : 0; 6940 } 6941 // Check keys. 6942 Inventory Item = A.FindInventory(class<Inventory>(CID)); 6943 if (Item) 6944 { 6945 return -1; 6946 } 6947 return -2; 6948} 6949 6950//========================================================================== 6951// 6952// StopSpeech 6953// 6954//========================================================================== 6955 6956final void StopSpeech() 6957{ 6958 CurrentSpeaker.Angles.yaw = OldSpeakerAngle; 6959 CurrentSpeaker = none; 6960 CurrentSpeakingTo = none; 6961 CurrentSpeechIndex = 0; 6962} 6963 6964//========================================================================== 6965// 6966// ConChoiceImpulse 6967// 6968//========================================================================== 6969 6970final void ConChoiceImpulse(int ChoiceNum) 6971{ 6972 RogueConSpeech *Speech; 6973 RogueConChoice *Choice; 6974 int SpeechNum; 6975 int Item1; 6976 int Item2; 6977 int Item3; 6978 class<EntityEx> ItemType; 6979 Inventory Item; 6980 bool GaveItem; 6981 6982 if (ConversationSlideshow) 6983 { 6984 // Resume conversation after slideshow. 6985 StartSpeech(CurrentSpeaker.GetSpeech()); 6986 ConversationSlideshow = false; 6987 return; 6988 } 6989 if (!CurrentSpeaker || !CurrentSpeechIndex) 6990 { 6991 return; 6992 } 6993 if (!ChoiceNum) 6994 { 6995 StopSpeech(); 6996 return; 6997 } 6998 if (CurrentSpeechIndex < 0) 6999 { 7000 Speech = &XLevel.GenericSpeeches[-CurrentSpeechIndex - 1]; 7001 } 7002 else 7003 { 7004 Speech = &XLevel.LevelSpeeches[CurrentSpeechIndex - 1]; 7005 } 7006 Choice = &Speech->Choices[ChoiceNum - 1]; 7007 // Check if player has needed items. 7008 Item1 = CheckForNeededItem(CurrentSpeakingTo, Choice->NeedItem1, Choice->NeedAmount1); 7009 Item2 = CheckForNeededItem(CurrentSpeakingTo, Choice->NeedItem2, Choice->NeedAmount2); 7010 Item3 = CheckForNeededItem(CurrentSpeakingTo, Choice->NeedItem3, Choice->NeedAmount3); 7011 if (Item1 == -2 || Item2 == -2 || Item3 == -2) 7012 { 7013 CurrentSpeakingTo.Player.cprint(Choice->TextNo); 7014 StopSpeech(); 7015 return; 7016 } 7017 GaveItem = true; 7018 if (Choice->GiveItem > 0) 7019 { 7020 ItemType = GetClassFromID(Choice->GiveItem); 7021 if (ItemType) 7022 { 7023 Item = Inventory(Spawn(ItemType,,,, false)); 7024 // This shouldn't count for the item statistics 7025 if (Item.bCountItem) 7026 { 7027 Item.bCountItem = false; 7028 Level.TotalItems--; 7029 } 7030 GaveItem = Item.TryPickup(CurrentSpeakingTo); 7031 if (!GaveItem) 7032 { 7033 Item.Destroy(); 7034 } 7035 } 7036 } 7037 if (GaveItem) 7038 { 7039 if (Item1 != -1) 7040 { 7041 Item = CurrentSpeakingTo.FindInventory( 7042 class<Inventory>(GetClassFromID(Choice->NeedItem1))); 7043 Item.Amount -= Choice->NeedAmount1; 7044 if (Item.Amount <= 0) 7045 { 7046 Item.Destroy(); 7047 } 7048 } 7049 if (Item2 != -1) 7050 { 7051 Item = CurrentSpeakingTo.FindInventory( 7052 class<Inventory>(GetClassFromID(Choice->NeedItem2))); 7053 Item.Amount -= Choice->NeedAmount2; 7054 if (Item.Amount <= 0) 7055 { 7056 Item.Destroy(); 7057 } 7058 } 7059 if (Item3 != -1) 7060 { 7061 Item = CurrentSpeakingTo.FindInventory( 7062 class<Inventory>(GetClassFromID(Choice->NeedItem3))); 7063 Item.Amount -= Choice->NeedAmount3; 7064 if (Item.Amount <= 0) 7065 { 7066 Item.Destroy(); 7067 } 7068 } 7069 } 7070 if (Choice->Objectives) 7071 { 7072 PlayerEx(CurrentSpeakingTo.Player).SetObjectives(Choice->Objectives); 7073 } 7074 if (!GaveItem) 7075 { 7076 CurrentSpeakingTo.Player.cprint(Choice->TextNo); 7077 } 7078 else if (strcmp(Choice->TextOK, "") && strcmp(Choice->TextOK, "_")) 7079 { 7080 CurrentSpeakingTo.Player.cprint(Choice->TextOK); 7081 } 7082 if (Choice->Next < 0) 7083 { 7084 CurrentSpeaker.CurrentSpeech = -Choice->Next; 7085 if (!ConversationSlideshow) 7086 { 7087 StartSpeech(CurrentSpeaker.GetSpeech()); 7088 } 7089 } 7090 else 7091 { 7092 if (Choice->Next) 7093 { 7094 CurrentSpeaker.CurrentSpeech = Choice->Next; 7095 } 7096 StopSpeech(); 7097 ConversationSlideshow = false; 7098 } 7099} 7100 7101//========================================================================== 7102// 7103// AddPlayerCorpse 7104// 7105//========================================================================== 7106 7107final void AddPlayerCorpse(EntityEx Corpse) 7108{ 7109 if (bodyqueslot >= BodyQueSize) 7110 { 7111 // Too many player corpses - remove an old one 7112 if (bodyque[bodyqueslot % BodyQueSize]) 7113 { 7114 bodyque[bodyqueslot % BodyQueSize].Destroy(); 7115 } 7116 } 7117 bodyque[bodyqueslot % BodyQueSize] = Corpse; 7118 Corpse.Translation = XLevel.SetBodyQueueTrans( 7119 bodyqueslot % BodyQueSize, Corpse.Translation); 7120 bodyqueslot++; 7121} 7122 7123//========================================================================== 7124// 7125// ParticleEffect 7126// 7127//========================================================================== 7128 7129void ParticleEffect(int count, int type1, int type2, TVec origin, float ornd, 7130 TVec velocity, float vrnd, float acceleration, float grav, int colour, float duration, float ramp) 7131{ 7132 int i; 7133 particle_t *p; 7134 7135 for (i = 0; i < count; i++) 7136 { 7137 p = Level.NewParticle(); 7138 if (!p) 7139 return; 7140 7141 p->die = Level.XLevel.Time + duration * Random(); 7142 p->colour = colour; 7143 p->Size = 1.0; 7144 if(ramp) 7145 p->ramp = Random() * ramp; 7146 7147 if(type2) 7148 { 7149 // Choose between the two types 7150 if (i & 1) 7151 { 7152 p->type = type1; 7153 } 7154 else 7155 { 7156 p->type = type2; 7157 } 7158 } 7159 else 7160 { 7161 p->type = type1; 7162 } 7163 7164 // No random origin 7165 if(!ornd) 7166 { 7167 p->org = origin; 7168 } 7169 else 7170 { 7171 p->org.x = origin.x + ((Random() * ornd) - ornd / 2.0); 7172 p->org.y = origin.y + ((Random() * ornd) - ornd / 2.0); 7173 p->org.z = origin.z + ((Random() * ornd) - ornd / 2.0); 7174 } 7175 7176 // No random velocity 7177 if(!vrnd) 7178 { 7179 p->vel = velocity; 7180 } 7181 else 7182 { 7183 p->vel.x = velocity.x * (Random() - vrnd); 7184 p->vel.y = velocity.y * (Random() - vrnd); 7185 p->vel.z = velocity.z * (Random() - vrnd); 7186 } 7187 p->accel.x = acceleration; 7188 p->accel.y = acceleration; 7189 p->accel.z = acceleration; 7190 p->gravity = grav; 7191 } 7192} 7193 7194//========================================================================== 7195// 7196// UpdateParticle 7197// 7198//========================================================================== 7199 7200void UpdateParticle(particle_t * p, float DeltaTime) 7201{ 7202 float time2, time3; 7203 float dvel; 7204 float grav; 7205 7206 time2 = DeltaTime * 10.0; // 15; 7207 time3 = DeltaTime * 15.0; 7208 dvel = 4.0 * DeltaTime; 7209 grav = DeltaTime * p->gravity /*sv_gravity.value * 0.05*/; 7210 p->vel.z -= grav; 7211 7212 switch (p->type) 7213 { 7214 case pt_static: 7215 p->vel += p->accel * DeltaTime; 7216 break; 7217 7218 case pt_explode: 7219 p->ramp += time2; 7220 if (p->ramp >= 16.0) 7221 p->die = -1.0; 7222 else 7223 p->colour = LineSpecialGameInfo.default.ramp1[ftoi(p->ramp)]; 7224 p->vel.x += p->vel.x * dvel; 7225 p->vel.y += p->vel.y * dvel; 7226 p->vel.z += p->vel.z * dvel; 7227 p->vel += p->accel * DeltaTime; 7228 p->vel.z -= grav; 7229 break; 7230 7231 case pt_explode2: 7232 p->ramp += time3; 7233 if (p->ramp >= 16.0) 7234 p->die = -1.0; 7235 else 7236 p->colour = LineSpecialGameInfo.default.ramp2[ftoi(p->ramp)]; 7237 p->vel.x -= p->vel.x * DeltaTime; 7238 p->vel.y -= p->vel.y * DeltaTime; 7239 p->vel.z -= p->vel.z * DeltaTime; 7240 p->vel += p->accel * DeltaTime; 7241 p->vel.z -= grav; 7242 break; 7243 7244 case pt_fountain: 7245 p->vel += p->accel * DeltaTime; 7246 p->colour = (p->colour & 0x00ffffff) | (ftoi( 7247 itof(p->colour >> 24) - 255.0 / 51.0 * 35.0 * DeltaTime) << 24); 7248 break; 7249 7250 case pt_spark: 7251 p->vel += p->accel * DeltaTime; 7252 p->colour = (p->colour & 0x00ffffff) | (ftoi( 7253 itof(p->colour >> 24) - 255.0 / 10.0 * 35.0 * DeltaTime) << 24); 7254 break; 7255 7256 case pt_ice_chunk: 7257 p->vel.x -= p->vel.x * DeltaTime; 7258 p->vel.y -= p->vel.y * DeltaTime; 7259 p->vel.z += p->accel.z * DeltaTime; 7260 break; 7261 7262 case pt_rail: 7263 p->vel += p->accel * DeltaTime; 7264 p->colour = (p->colour & 0x00ffffff) | (ftoi( 7265 itof(p->colour >> 24) - 255.0 * DeltaTime) << 24); 7266 break; 7267 } 7268} 7269 7270//========================================================================== 7271// 7272// AcsFadeRange 7273// 7274//========================================================================== 7275 7276void AcsFadeRange(float BlendR1, float BlendG1, float BlendB1, float BlendA1, 7277 float BlendR2, float BlendG2, float BlendB2, float BlendA2, 7278 float Duration, Entity Activator) 7279{ 7280 if (Activator) 7281 { 7282 if (!Activator.bIsPlayer) 7283 { 7284 return; 7285 } 7286 StartFlashFader(BlendR1, BlendG1, BlendB1, BlendA1, BlendR2, 7287 BlendG2, BlendB2, BlendA2, Duration, Activator); 7288 } 7289 else 7290 { 7291 BasePlayer P; 7292 foreach AllActivePlayers(P) 7293 { 7294 StartFlashFader(BlendR1, BlendG1, BlendB1, BlendA1, BlendR2, 7295 BlendG2, BlendB2, BlendA2, Duration, P.MO); 7296 } 7297 } 7298} 7299 7300//========================================================================== 7301// 7302// StartFlashFader 7303// 7304//========================================================================== 7305 7306final void StartFlashFader(float BlendR1, float BlendG1, float BlendB1, 7307 float BlendA1, float BlendR2, float BlendG2, float BlendB2, 7308 float BlendA2, float Duration, Entity ForWho) 7309{ 7310 PlayerEx P = PlayerEx(ForWho.Player); 7311 if (Duration <= 0.0) 7312 { 7313 P.BlendR = BlendR2; 7314 P.BlendG = BlendG2; 7315 P.BlendB = BlendB2; 7316 P.BlendA = BlendA2; 7317 } 7318 else 7319 { 7320 if (BlendA1 < 0.0) 7321 { 7322 if (P.BlendA <= 0.0) 7323 { 7324 BlendR1 = BlendR2; 7325 BlendG1 = BlendG2; 7326 BlendB1 = BlendB2; 7327 BlendA1 = 0.0; 7328 } 7329 else 7330 { 7331 BlendR1 = P.BlendR; 7332 BlendG1 = P.BlendG; 7333 BlendB1 = P.BlendB; 7334 BlendA1 = P.BlendA; 7335 } 7336 } 7337 FlashFader F = Spawn(FlashFader); 7338 F.Init(BlendR1, BlendG1, BlendB1, BlendA1, BlendR2, BlendG2, BlendB2, 7339 BlendA2, Duration, EntityEx(ForWho)); 7340 } 7341} 7342 7343//========================================================================== 7344// 7345// AcsCancelFade 7346// 7347//========================================================================== 7348 7349void AcsCancelFade(Entity Activator) 7350{ 7351 Thinker Th; 7352 foreach AllThinkers(FlashFader, Th) 7353 { 7354 if (!Activator || FlashFader(Th).ForWho == Activator) 7355 { 7356 FlashFader(Th).Cancel(); 7357 } 7358 } 7359} 7360 7361//========================================================================== 7362// 7363// P_Massacre 7364// 7365// Kills all monsters. 7366// 7367//========================================================================== 7368 7369final int P_Massacre() 7370{ 7371 int count; 7372 EntityEx mo; 7373 7374 count = 0; 7375 foreach AllThinkers(EntityEx, mo) 7376 { 7377 if (mo.bMonster && mo.Health > 0) 7378 { 7379 mo.bNonShootable = false; 7380 mo.bInvulnerable = false; 7381 mo.bShootable = true; 7382 mo.Damage(none, none, 10000); 7383 count++; 7384 } 7385 } 7386 return count; 7387} 7388 7389//========================================================================== 7390// 7391// SetMarineWeapon 7392// 7393//========================================================================== 7394 7395final void SetMarineWeapon(int Tid, int Weapon, Entity Activator) 7396{ 7397 if (Tid) 7398 { 7399 Entity Ent; 7400 for (Ent = Level.FindMobjFromTID(Tid, none); Ent; 7401 Ent = Level.FindMobjFromTID(Tid, Ent)) 7402 { 7403 if (ScriptedMarine(Ent)) 7404 { 7405 ScriptedMarine(Ent).SetWeapon(Weapon); 7406 } 7407 } 7408 } 7409 else if (ScriptedMarine(Activator)) 7410 { 7411 ScriptedMarine(Activator).SetWeapon(Weapon); 7412 } 7413} 7414 7415//========================================================================== 7416// 7417// SetMarineSprite 7418// 7419//========================================================================== 7420 7421final void SetMarineSprite(int Tid, name SrcClassName, Entity Activator) 7422{ 7423 // If there's no such class, print message and do nothing. 7424 class TmpClass = FindClass(SrcClassName); 7425 if (!TmpClass) 7426 { 7427 print("Unknown class %n", SrcClassName); 7428 return; 7429 } 7430 // If it's not a valid actor class, it will set sprite back to default. 7431 class<EntityEx> SrcClass = class<EntityEx>(TmpClass); 7432 7433 if (Tid) 7434 { 7435 Entity Ent; 7436 for (Ent = Level.FindMobjFromTID(Tid, none); Ent; 7437 Ent = Level.FindMobjFromTID(Tid, Ent)) 7438 { 7439 if (ScriptedMarine(Ent)) 7440 { 7441 ScriptedMarine(Ent).SetSprite(SrcClass); 7442 } 7443 } 7444 } 7445 else if (ScriptedMarine(Activator)) 7446 { 7447 ScriptedMarine(Activator).SetSprite(SrcClass); 7448 } 7449} 7450 7451//========================================================================== 7452// 7453// GetDefaultDoorSound 7454// 7455//========================================================================== 7456 7457name GetDefaultDoorSound(sector_t* Sector) 7458{ 7459 return DefaultDoorSound; 7460} 7461 7462//========================================================================== 7463// 7464// GetClassSpawnFlags 7465// 7466//========================================================================== 7467 7468int GetPClassSpawnFlags() 7469{ 7470 return 0; 7471} 7472 7473//========================================================================== 7474// 7475// GetDehackedItemType 7476// 7477//========================================================================== 7478 7479class<Inventory> GetDehackedItemType(EntityEx Ent) 7480{ 7481 return none; 7482} 7483 7484defaultproperties 7485{ 7486 ExtPlayersBase = 4001; 7487 Lock103Message = "That doesn't seem to work"; 7488 BodyQueSize = BODYQUESIZE; 7489 CorpseQueSize = CORPSEQUEUESIZE; 7490 DefaultDoorSound = 'DoorNormal'; 7491 DefaultCeilingSound = 'CeilingNormal'; 7492 DefaultSilentCeilingSound = 'CeilingSemiSilent'; 7493 DefaultFloorSound = 'Floor'; 7494 DefaultFloorAltSound = 'Floor'; 7495 DefaultStairStepSound = 'Floor'; 7496 DefaultPlatformSound = 'Platform'; 7497} 7498