SetupWishCore: fixed table lookup being out of bounds for the wisest pcs
[gemrb.git] / gemrb / core / GameScript / GSUtils.cpp
blobf5e12cfe791d0f2becaaae2eb8d519f7e88eea03
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2005 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "GameScript/GSUtils.h"
22 #include "GameScript/Matching.h"
24 #include "strrefs.h"
25 #include "defsounds.h"
27 #include "Audio.h"
28 #include "DialogHandler.h"
29 #include "DisplayMessage.h"
30 #include "Game.h"
31 #include "GameData.h"
32 #include "Interface.h"
33 #include "Item.h"
34 #include "Map.h"
35 #include "Spell.h"
36 #include "StringMgr.h"
37 #include "TileMap.h"
38 #include "Video.h"
39 #include "GUI/GameControl.h"
41 #include <cstdio>
43 //these tables will get freed by Core
44 Holder<SymbolMgr> triggersTable;
45 Holder<SymbolMgr> actionsTable;
46 Holder<SymbolMgr> objectsTable;
47 TriggerFunction triggers[MAX_TRIGGERS];
48 ActionFunction actions[MAX_ACTIONS];
49 short actionflags[MAX_ACTIONS];
50 short triggerflags[MAX_TRIGGERS];
51 ObjectFunction objects[MAX_OBJECTS];
52 IDSFunction idtargets[MAX_OBJECT_FIELDS];
53 Cache SrcCache; //cache for string resources (pst)
54 Cache BcsCache; //cache for scripts
55 int ObjectIDSCount = 7;
56 int MaxObjectNesting = 5;
57 bool HasAdditionalRect = false;
58 bool HasTriggerPoint = false;
59 //released by ReleaseMemory
60 ieResRef *ObjectIDSTableNames;
61 int ObjectFieldsCount = 7;
62 int ExtraParametersCount = 0;
63 int InDebug = 0;
64 int happiness[3][20];
65 int RandomNumValue;
66 int *SkillStats=NULL;
67 int SkillCount=-1;
68 // reaction modifiers (by reputation and charisma)
69 int rmodrep[20];
70 int rmodchr[25];
71 Gem_Polygon **polygons;
73 void InitScriptTables()
75 //initializing the skill->stats conversion table
77 AutoTable tab("skillsta");
78 if (tab) {
79 int rowcount = tab->GetRowCount();
80 SkillCount = rowcount;
81 if (rowcount) {
82 SkillStats = (int *) malloc(rowcount * sizeof(int) );
83 while(rowcount--) {
84 SkillStats[rowcount]=strtol(tab->QueryField(rowcount,0), NULL, 0);
89 //initializing the happiness table
91 AutoTable tab("happy");
92 if (tab) {
93 for (int alignment=0;alignment<3;alignment++) {
94 for (int reputation=0;reputation<20;reputation++) {
95 happiness[alignment][reputation]=strtol(tab->QueryField(reputation,alignment), NULL, 0);
101 //initializing the reaction mod. reputation table
102 AutoTable rmr("rmodrep");
103 if (rmr) {
104 for (int reputation=0; reputation<20; reputation++) {
105 rmodrep[reputation] = strtol(rmr->QueryField(0, reputation), NULL, 0);
109 //initializing the reaction mod. charisma table
110 AutoTable rmc("rmodchr");
111 if (rmc) {
112 for (int charisma=0; charisma<25; charisma++) {
113 rmodchr[charisma] = strtol(rmc->QueryField(0, charisma), NULL, 0);
118 int GetReaction(Actor *target, Scriptable *Sender)
120 int chr, rep, reaction;
121 chr = target->GetStat(IE_CHR)-1;
122 if (target->GetStat(IE_EA) == EA_PC) {
123 rep = core->GetGame()->Reputation/10;
124 } else {
125 rep = target->GetStat(IE_REPUTATION);
127 reaction = 10 + rmodrep[rep] + rmodchr[chr];
129 // add -4 penalty when dealing with racial enemies
130 if (Sender && target->GetRangerLevel() && Sender->Type == ST_ACTOR && target->IsRacialEnemy((Actor *)Sender)) {
131 reaction -= 4;
134 return reaction;
137 int GetHappiness(Scriptable* Sender, int reputation)
139 if (Sender->Type != ST_ACTOR) {
140 return 0;
142 Actor* ab = ( Actor* ) Sender;
143 int alignment = ab->GetStat(IE_ALIGNMENT)&AL_GE_MASK; //good / evil
144 if (reputation > 200) {
145 reputation = 200;
147 return happiness[alignment][reputation/10-1];
150 int GetHPPercent(Scriptable* Sender)
152 if (Sender->Type != ST_ACTOR) {
153 return 0;
155 Actor* ab = ( Actor* ) Sender;
156 int hp1 = ab->GetStat(IE_MAXHITPOINTS);
157 if (hp1<1) {
158 return 0;
160 int hp2 = ab->GetBase(IE_HITPOINTS);
161 if (hp2<1) {
162 return 0;
164 return hp2*100/hp1;
167 void HandleBitMod(ieDword &value1, ieDword value2, int opcode)
169 switch(opcode) {
170 case BM_AND:
171 value1 = ( value1& value2 );
172 break;
173 case BM_OR:
174 value1 = ( value1| value2 );
175 break;
176 case BM_XOR:
177 value1 = ( value1^ value2 );
178 break;
179 case BM_NAND: //this is a GemRB extension
180 value1 = ( value1& ~value2 );
181 break;
182 case BM_SET: //this is a GemRB extension
183 value1 = value2;
184 break;
188 // SPIT is not in the original engine spec, it is reserved for the
189 // enchantable items feature
190 // 0 1 2 3 4
191 static const char *spell_suffices[]={"SPIT","SPPR","SPWI","SPIN","SPCL"};
193 //this function handles the polymorphism of Spell[RES] actions
194 //it returns spellres
195 bool ResolveSpellName(ieResRef spellres, Action *parameters)
197 if (parameters->string0Parameter[0]) {
198 strnlwrcpy(spellres, parameters->string0Parameter, 8);
199 } else {
200 //resolve spell
201 int type = parameters->int0Parameter/1000;
202 int spellid = parameters->int0Parameter%1000;
203 if (type>4) {
204 return false;
206 sprintf(spellres, "%s%03d", spell_suffices[type], spellid);
208 return gamedata->Exists(spellres, IE_SPL_CLASS_ID);
211 void ResolveSpellName(ieResRef spellres, ieDword number)
213 //resolve spell
214 unsigned int type = number/1000;
215 int spellid = number%1000;
216 if (type>4) {
217 type=0;
219 sprintf(spellres, "%s%03d", spell_suffices[type], spellid);
222 ieDword ResolveSpellNumber(const ieResRef spellres)
224 int i;
226 for(i=0;i<5;i++) {
227 if(!strnicmp(spellres, spell_suffices[i], 4)) {
228 int n = -1;
229 sscanf(spellres+4,"%d", &n);
230 if (n<0) {
231 return 0xffffffff;
233 return i*1000+n;
236 return 0xffffffff;
239 bool ResolveItemName(ieResRef itemres, Actor *act, ieDword Slot)
241 CREItem *itm = act->inventory.GetSlotItem(Slot);
242 if(itm) {
243 strnlwrcpy(itemres, itm->ItemResRef, 8);
244 return gamedata->Exists(itemres, IE_ITM_CLASS_ID);
246 return false;
249 bool StoreHasItemCore(const ieResRef storename, const ieResRef itemname)
251 bool had_nostore=false;
252 bool has_current=false;
253 ieResRef current;
254 ieVariable owner;
255 CREItem item;
257 Store *store = core->GetCurrentStore();
258 if (!store) {
259 had_nostore = true;
260 store = core->SetCurrentStore(storename, NULL);
261 } else {
262 if (strnicmp(store->Name, storename, 8) ) {
263 //not the current store, we need some dirty hack
264 has_current = true;
265 strnlwrcpy(current, store->Name, 8);
266 strnuprcpy(owner, store->GetOwner(), 32);
269 if (!store) {
270 printMessage("GameScript","Store cannot be opened!\n", LIGHT_RED);
271 return false;
274 bool ret = false;
275 //don't use triggers (pst style), it would be possible to create infinite loops
276 if (store->FindItem(itemname, false) != (unsigned int)-1) {
277 ret=true;
279 if (has_current) {
280 //setting back old store (this will save our current store)
281 core->SetCurrentStore(current, owner);
282 } else if (had_nostore) {
283 core->CloseCurrentStore();
285 return ret;
288 //don't pass this point by reference, it is subject to change
289 void ClickCore(Scriptable *Sender, Point point, int type, int speed)
291 Map *map = Sender->GetCurrentArea();
292 if (!map) {
293 Sender->ReleaseCurrentAction();
294 return;
296 Point p=map->TMap->GetMapSize();
297 if (!p.PointInside(point)) {
298 Sender->ReleaseCurrentAction();
299 return;
301 Video *video = core->GetVideoDriver();
302 GlobalTimer *timer = core->timer;
303 timer->SetMoveViewPort( point.x, point.y, speed, true );
304 timer->DoStep(0);
305 if (timer->ViewportIsMoving()) {
306 Sender->AddActionInFront( Sender->GetCurrentAction() );
307 Sender->SetWait(1);
308 Sender->ReleaseCurrentAction();
309 return;
312 video->ConvertToScreen(point.x, point.y);
313 GameControl *win = core->GetGameControl();
315 point.x+=win->XPos;
316 point.y+=win->YPos;
317 video->MoveMouse(point.x, point.y);
318 video->ClickMouse(type);
319 Sender->ReleaseCurrentAction();
322 void TransformItemCore(Actor *actor, Action *parameters, bool onlyone)
324 int i = actor->inventory.GetSlotCount();
325 while(i--) {
326 CREItem *item = actor->inventory.GetSlotItem(i);
327 if (!item) {
328 continue;
330 if (strnicmp(item->ItemResRef, parameters->string0Parameter, 8) ) {
331 continue;
333 actor->inventory.SetSlotItemRes(parameters->string1Parameter,i,parameters->int0Parameter,parameters->int1Parameter,parameters->int2Parameter);
334 if (onlyone) {
335 break;
340 //check if an inventory (container or actor) has item (could be recursive ?)
341 bool HasItemCore(Inventory *inventory, const ieResRef itemname, ieDword flags)
343 if (inventory->HasItem(itemname, flags)) {
344 return true;
346 int i=inventory->GetSlotCount();
347 while (i--) {
348 //maybe we could speed this up if we mark bag items with a flags bit
349 CREItem *itemslot = inventory->GetSlotItem(i);
350 if (!itemslot)
351 continue;
352 Item *item = gamedata->GetItem(itemslot->ItemResRef);
353 if (!item)
354 continue;
355 bool ret = false;
356 if (core->CanUseItemType(SLOT_BAG,item,NULL) ) {
357 //the store is the same as the item's name
358 ret = StoreHasItemCore(itemslot->ItemResRef, itemname);
360 gamedata->FreeItem(item, itemslot->ItemResRef);
361 if (ret) {
362 return true;
365 return false;
368 void DisplayStringCore(Scriptable* Sender, int Strref, int flags)
370 StringBlock sb;
371 char Sound[_MAX_PATH];
373 //no one hears you when you are in the Limbo!
374 if (!Sender->GetCurrentArea()) {
375 return;
378 memset(&sb,0,sizeof(sb));
379 Sound[0]=0;
380 printf( "Displaying string on: %s\n", Sender->GetScriptName() );
381 if (flags & DS_CONST) {
382 if (Sender->Type!=ST_ACTOR) {
383 printMessage("GameScript","Verbal constant not supported for non actors!\n", LIGHT_RED);
384 return;
386 Actor* actor = ( Actor* ) Sender;
387 if ((ieDword) Strref>=VCONST_COUNT) {
388 printMessage("GameScript","Invalid verbal constant!\n", LIGHT_RED);
389 return;
392 int tmp=(int) actor->GetVerbalConstant(Strref);
393 if (tmp <= 0 || (actor->GetStat(IE_MC_FLAGS) & MC_EXPORTABLE)) {
394 //get soundset based string constant
395 actor->ResolveStringConstant( sb.Sound, (unsigned int) Strref);
396 if (actor->PCStats && actor->PCStats->SoundFolder[0]) {
397 snprintf(Sound, _MAX_PATH, "%s/%s",
398 actor->PCStats->SoundFolder, sb.Sound);
399 } else {
400 memcpy(Sound, sb.Sound, sizeof(ieResRef) );
403 Strref = tmp;
405 //display the verbal constants in the console
406 ieDword charactersubtitles = 0;
407 core->GetDictionary()->Lookup("Subtitles", charactersubtitles);
408 if (charactersubtitles) {
409 flags |= DS_CONSOLE;
413 if ((Strref != -1) && !sb.Sound[0]) {
414 sb = core->strings->GetStringBlock( Strref );
415 memcpy(Sound, sb.Sound, sizeof(ieResRef) );
416 if (sb.text[0] && strcmp(sb.text," ") && (flags & DS_CONSOLE)) {
417 //can't play the sound here, we have to delay action
418 //and for that, we have to know how long the text takes
419 if(flags&DS_NONAME) {
420 displaymsg->DisplayString( sb.text );
421 } else {
422 displaymsg->DisplayStringName( Strref, 0xf0f0f0, Sender, 0);
425 if (sb.text[0] && strcmp(sb.text," ") && (flags & (DS_HEAD | DS_AREA))) {
426 Sender->DisplayHeadText( sb.text );
427 //don't free sb.text, it is residing in Sender
428 if (flags & DS_AREA) {
429 Sender->FixHeadTextPos();
431 } else {
432 core->FreeString( sb.text );
435 if (Sound[0] && !(flags&DS_SILENT) ) {
436 ieDword speech = GEM_SND_RELATIVE; //disable position
437 if (flags&DS_SPEECH) speech|=GEM_SND_SPEECH;
438 ieDword len = core->GetAudioDrv()->Play( Sound,0,0,speech );
439 ieDword counter = ( AI_UPDATE_TIME * len ) / 1000;
440 if ((counter != 0) && (flags &DS_WAIT) )
441 Sender->SetWait( counter );
445 int CanSee(Scriptable* Sender, Scriptable* target, bool range, int seeflag)
447 Map *map;
449 if (target->Type==ST_ACTOR) {
450 Actor *tar = (Actor *) target;
452 if (!tar->ValidTarget(seeflag)) {
453 return 0;
457 map = target->GetCurrentArea();
458 //if (!(seeflag&GA_GLOBAL)) {
459 if ( map!=Sender->GetCurrentArea() ) {
460 return 0;
464 if (range) {
465 unsigned int dist;
467 if (Sender->Type == ST_ACTOR) {
468 Actor* snd = ( Actor* ) Sender;
469 dist = snd->Modified[IE_VISUALRANGE];
470 } else {
471 dist = 30;
474 if (Distance(target->Pos, Sender->Pos) > dist * 15) {
475 return 0;
479 return map->IsVisible(target->Pos, Sender->Pos);
482 //non actors can see too (reducing function to LOS)
483 //non actors can be seen too (reducing function to LOS)
484 int SeeCore(Scriptable* Sender, Trigger* parameters, int justlos)
486 //see dead
487 int flags;
489 if (parameters->int0Parameter) {
490 flags = GA_DETECT;
491 } else {
492 flags = GA_NO_DEAD;
494 Scriptable* tar = GetActorFromObject( Sender, parameters->objectParameter, flags );
495 /* don't set LastSeen if this isn't an actor */
496 if (!tar) {
497 return 0;
499 //both are actors
500 if (CanSee(Sender, tar, true, flags) ) {
501 if (justlos) {
502 return 1;
504 if (Sender->Type==ST_ACTOR && tar->Type==ST_ACTOR) {
505 Actor* snd = ( Actor* ) Sender;
506 //additional checks for invisibility?
507 snd->LastSeen = ((Actor *) tar)->GetID();
509 return 1;
511 return 0;
514 //transfering item from Sender to target
515 //if target has no inventory, the item will be destructed
516 //if target can't get it, it will be dropped at its feet
517 int MoveItemCore(Scriptable *Sender, Scriptable *target, const char *resref, int flags, int setflag)
519 Inventory *myinv;
520 Map *map;
521 // track whether we are dealing with our party and need to display feedback
522 bool lostitem = false;
523 bool gotitem = false;
525 if (!target) {
526 return MIC_INVALID;
528 map=Sender->GetCurrentArea();
529 switch(Sender->Type) {
530 case ST_ACTOR:
531 myinv=&((Actor *) Sender)->inventory;
532 if (((Actor *)Sender)->InParty) lostitem = true;
533 break;
534 case ST_CONTAINER:
535 myinv=&((Container *) Sender)->inventory;
536 break;
537 default:
538 return MIC_INVALID;
540 CREItem *item;
541 myinv->RemoveItem(resref, flags, &item);
542 if (!item) {
543 // nothing was removed
544 return MIC_NOITEM;
547 item->Flags|=setflag;
549 switch(target->Type) {
550 case ST_ACTOR:
551 myinv=&((Actor *) target)->inventory;
552 if (((Actor *) target)->InParty) gotitem = true;
553 break;
554 case ST_CONTAINER:
555 myinv=&((Container *) target)->inventory;
556 break;
557 default:
558 myinv = NULL;
559 break;
561 if (!myinv) {
562 delete item;
563 if (lostitem) displaymsg->DisplayConstantString(STR_LOSTITEM, 0xbcefbc);
564 return MIC_GOTITEM; // actually it was lost, not gained
566 if ( myinv->AddSlotItem(item, SLOT_ONLYINVENTORY) !=ASI_SUCCESS) {
567 // drop it at my feet
568 map->AddItemToLocation(target->Pos, item);
569 if (gotitem) displaymsg->DisplayConstantString(STR_INVFULL_ITEMDROP, 0xbcefbc);
570 return MIC_FULL;
572 if (gotitem) displaymsg->DisplayConstantString(STR_GOTITEM, 0xbcefbc);
573 return MIC_GOTITEM;
576 /*FIXME: what is 'base'*/
577 void PolymorphCopyCore(Actor *src, Actor *tar, bool base)
579 tar->SetBase(IE_ANIMATION_ID, src->GetStat(IE_ANIMATION_ID) );
580 if (!base) {
581 tar->SetBase(IE_ARMOR_TYPE, src->GetStat(IE_ARMOR_TYPE) );
582 for (int i=0;i<7;i++) {
583 tar->SetBase(IE_COLORS+i, src->GetStat(IE_COLORS+i) );
586 tar->SetName(src->GetName(0),0);
587 tar->SetName(src->GetName(1),1);
588 //add more attribute copying
591 void CreateCreatureCore(Scriptable* Sender, Action* parameters, int flags)
593 Scriptable *tmp = GetActorFromObject( Sender, parameters->objects[1] );
594 //if there is nothing to copy, don't spawn anything
595 if (flags & CC_COPY) {
596 if (!tmp || tmp->Type != ST_ACTOR) {
597 return;
601 Actor* ab;
602 if (flags & CC_STRING1) {
603 ab = gamedata->GetCreature(parameters->string1Parameter);
605 else {
606 ab = gamedata->GetCreature(parameters->string0Parameter);
609 if (!ab) {
610 printMessage("GameScript","Failed to create creature! ",LIGHT_RED);
611 printf("(missing creature file %s?)\n", parameters->string0Parameter);
612 // maybe this should abort()?
613 return;
616 //iwd2 allows an optional scriptname to be set
617 //but bg2 doesn't have this feature
618 //this way it works for both games
619 if ((flags & CC_SCRIPTNAME) && parameters->string1Parameter[0]) {
620 ab->SetScriptName(parameters->string1Parameter);
623 int radius;
624 Point pnt;
626 radius=0;
627 switch (flags & CC_MASK) {
628 //creates creature just off the screen
629 case CC_OFFSCREEN:
631 Region vp = core->GetVideoDriver()->GetViewport();
632 radius=vp.w/2; //actually it must be further divided by the tile size, hmm 16?
634 //falling through
635 case CC_OBJECT://use object + offset
636 if (tmp) Sender=tmp;
637 //falling through
638 case CC_OFFSET://use sender + offset
639 pnt.x = parameters->pointParameter.x+Sender->Pos.x;
640 pnt.y = parameters->pointParameter.y+Sender->Pos.y;
641 break;
642 default: //absolute point, but -1,-1 means AtFeet
643 pnt.x = parameters->pointParameter.x;
644 pnt.y = parameters->pointParameter.y;
645 if (pnt.isempty()) {
646 pnt.x = Sender->Pos.x;
647 pnt.y = Sender->Pos.y;
649 break;
652 Map *map = Sender->GetCurrentArea();
653 map->AddActor( ab );
654 ab->SetPosition( pnt, flags&CC_CHECK_IMPASSABLE, radius );
655 ab->SetOrientation(parameters->int0Parameter, false );
657 //if string1 is animation, then we can't use it for a DV too
658 if (flags & CC_PLAY_ANIM) {
659 CreateVisualEffectCore( ab, ab->Pos, parameters->string1Parameter, 1);
660 } else {
661 //setting the deathvariable if it exists (iwd2)
662 if (parameters->string1Parameter[0]) {
663 ab->SetScriptName(parameters->string1Parameter);
667 if (flags & CC_COPY) {
668 PolymorphCopyCore ( (Actor *) tmp, ab, false);
672 static ScriptedAnimation *GetVVCEffect(const char *effect, int iterations)
674 if (effect[0]) {
675 ScriptedAnimation* vvc = gamedata->GetScriptedAnimation(effect, false);
676 if (!vvc) {
677 printMessage("GameScript","Failed to create effect.",LIGHT_RED);
678 return NULL;
680 if (iterations) {
681 vvc->SetDefaultDuration( vvc->GetSequenceDuration(AI_UPDATE_TIME * iterations));
682 } else {
683 vvc->PlayOnce();
685 return vvc;
687 return NULL;
690 void CreateVisualEffectCore(Actor *target, const char *effect, int iterations)
692 ScriptedAnimation *vvc = GetVVCEffect(effect, iterations);
693 if (vvc) {
694 target->AddVVCell( vvc );
698 void CreateVisualEffectCore(Scriptable *Sender, const Point &position, const char *effect, int iterations)
700 ScriptedAnimation *vvc = GetVVCEffect(effect, iterations);
701 if (vvc) {
702 vvc->XPos +=position.x;
703 vvc->YPos +=position.y;
704 Sender->GetCurrentArea( )->AddVVCell( vvc );
708 //this destroys the current actor and replaces it with another
709 void ChangeAnimationCore(Actor *src, const char *resref, bool effect)
711 Actor *tar = gamedata->GetCreature(resref);
712 if (tar) {
713 Map *map = src->GetCurrentArea();
714 map->AddActor( tar );
715 Point pos = src->Pos;
716 tar->SetOrientation(src->GetOrientation(), false );
717 src->DestroySelf();
718 // can't SetPosition while the old actor is taking the spot
719 tar->SetPosition(pos, 1);
720 if (effect) {
721 CreateVisualEffectCore(tar, tar->Pos,"smokepuffeffect",1);
726 void EscapeAreaCore(Scriptable* Sender, const Point &p, const char* area, const Point &enter, int flags, int wait)
728 char Tmp[256];
730 if ( !p.isempty() && PersonalDistance(p, Sender)>MAX_OPERATING_DISTANCE) {
731 //MoveNearerTo will return 0, if the actor is in move
732 //it will return 1 (the fourth parameter) if the target is unreachable
733 if (!MoveNearerTo(Sender, p, MAX_OPERATING_DISTANCE,1) ) {
734 if (!Sender->InMove()) printf("At least it said so...\n");
735 return;
739 if (flags &EA_DESTROY) {
740 //this must be put into a non-const variable
741 sprintf( Tmp, "DestroySelf()" );
742 } else {
743 // last parameter is 'face', which should be passed from relevant action parameter..
744 sprintf( Tmp, "MoveBetweenAreas(\"%s\",[%hd.%hd],%d)", area, enter.x, enter.y, 0 );
746 printMessage("GSUtils"," ", WHITE);
747 printf("Executing %s in EscapeAreaCore\n", Tmp);
748 //drop this action, but add another (destroyself or movebetweenareas)
749 //between the arrival and the final escape, there should be a wait time
750 //that wait time could be handled here
751 if (wait) {
752 printf("But wait a bit... (%d)\n", wait);
753 Sender->SetWait(wait);
755 Sender->ReleaseCurrentAction();
756 Action * action = GenerateAction( Tmp);
757 Sender->AddActionInFront( action );
760 void GetTalkPositionFromScriptable(Scriptable* scr, Point &position)
762 switch (scr->Type) {
763 case ST_AREA: case ST_GLOBAL:
764 position = scr->Pos; //fake
765 break;
766 case ST_ACTOR:
767 //if there are other moveables, put them here
768 position = ((Movable *) scr)->GetMostLikelyPosition();
769 break;
770 case ST_TRIGGER: case ST_PROXIMITY: case ST_TRAVEL:
771 if (((InfoPoint *) scr)->Flags & TRAP_USEPOINT) {
772 position=((InfoPoint *) scr)->UsePoint;
773 break;
775 position=((InfoPoint *) scr)->TrapLaunch;
776 break;
777 case ST_DOOR: case ST_CONTAINER:
778 position=((Highlightable *) scr)->TrapLaunch;
779 break;
783 void GetPositionFromScriptable(Scriptable* scr, Point &position, bool dest)
785 if (!dest) {
786 position = scr->Pos;
787 return;
789 switch (scr->Type) {
790 case ST_AREA: case ST_GLOBAL:
791 position = scr->Pos; //fake
792 break;
793 case ST_ACTOR:
794 //if there are other moveables, put them here
795 position = ((Movable *) scr)->GetMostLikelyPosition();
796 break;
797 case ST_TRIGGER: case ST_PROXIMITY: case ST_TRAVEL:
798 if (((InfoPoint *) scr)->Flags & TRAP_USEPOINT) {
799 position=((InfoPoint *) scr)->UsePoint;
800 break;
802 case ST_DOOR: case ST_CONTAINER:
803 position=((Highlightable *) scr)->TrapLaunch;
807 int CheckInteract(const char *talker, const char *target)
809 AutoTable interact("interact");
810 if(!interact)
811 return 0;
812 const char *value = interact->QueryField(talker, target);
813 if(!value)
814 return 0;
815 switch(value[0]) {
816 case 's':
817 return I_SPECIAL;
818 case 'c':
819 return I_COMPLIMENT;
820 case 'i':
821 return I_INSULT;
823 return 0;
826 static ieResRef PlayerDialogRes = "PLAYERx\0";
828 void BeginDialog(Scriptable* Sender, Action* parameters, int Flags)
830 Scriptable* tar, *scr;
831 int seeflag = GA_NO_DEAD;
833 if (InDebug&ID_VARIABLES) {
834 printf("BeginDialog core\n");
836 if (Flags & BD_OWN) {
837 tar = GetStoredActorFromObject( Sender, parameters->objects[1], seeflag);
838 scr = tar;
839 } else {
840 tar = GetStoredActorFromObject( Sender, parameters->objects[1], seeflag);
841 scr = Sender;
843 if (!scr) {
844 printMessage("GameScript"," ",LIGHT_RED);
845 printf("Speaker for dialog couldn't be found (Sender: %s, Type: %d) Flags:%d.\n", Sender->GetScriptName(), Sender->Type, Flags);
846 Sender->ReleaseCurrentAction();
847 return;
850 if (!tar || tar->Type!=ST_ACTOR) {
851 printMessage("GameScript"," ",LIGHT_RED);
852 printf("Target for dialog couldn't be found (Sender: %s, Type: %d).\n", Sender->GetScriptName(), Sender->Type);
853 if (Sender->Type == ST_ACTOR) {
854 ((Actor *) Sender)->DebugDump();
856 printf ("Target object: ");
857 if (parameters->objects[1]) {
858 parameters->objects[1]->Dump();
859 } else {
860 printf("<NULL>\n");
862 Sender->ReleaseCurrentAction();
863 return;
866 Actor *speaker, *target;
868 speaker = NULL;
869 target = (Actor *) tar;
870 if ((Flags & BD_CHECKDIST) && !CanSee(scr, target, false, seeflag) ) {
871 printMessage("GameScript"," ",LIGHT_RED);
872 printf("CanSee returned false! Speaker (%s, type %d) and target are:\n", scr->GetScriptName(), scr->Type);
873 if (scr->Type == ST_ACTOR) {
874 ((Actor *) scr)->DebugDump();
876 ((Actor *) tar)->DebugDump();
877 Sender->ReleaseCurrentAction();
878 return;
880 bool swap = false;
881 if (scr->Type==ST_ACTOR) {
882 speaker = (Actor *) scr;
883 if (speaker->GetStat(IE_STATE_ID)&STATE_DEAD) {
884 printMessage("GameScript"," ",LIGHT_RED);
885 printf("Speaker is dead, cannot start dialogue. Speaker and target are:\n");
886 speaker->DebugDump();
887 target->DebugDump();
888 Sender->ReleaseCurrentAction();
889 return;
891 ieDword range = MAX_OPERATING_DISTANCE;
892 //making sure speaker is the protagonist, player, actor
893 if ( target->InParty == 1) swap = true;
894 else if ( speaker->InParty !=1 && target->InParty) swap = true;
895 //CHECKDIST works only for mobile scriptables
896 if (Flags&BD_CHECKDIST) {
897 if ( scr->GetCurrentArea()!=target->GetCurrentArea() ||
898 PersonalDistance(scr, target)>range) {
899 MoveNearerTo(Sender, target, MAX_OPERATING_DISTANCE);
900 return;
903 } else {
904 //pst style dialog with trigger points
905 swap=true;
906 if (Flags&BD_CHECKDIST) {
907 Point TalkPos;
909 if (target->InMove()) {
910 //waiting for target
911 Sender->AddActionInFront( Sender->GetCurrentAction() );
912 Sender->ReleaseCurrentAction();
913 Sender->SetWait(1);
914 return;
916 GetTalkPositionFromScriptable(scr, TalkPos);
917 if (PersonalDistance(TalkPos, target)>MAX_OPERATING_DISTANCE ) {
918 //try to force the target to come closer???
919 GoNear(target, TalkPos);
920 Sender->AddActionInFront( Sender->GetCurrentAction() );
921 Sender->ReleaseCurrentAction();
922 Sender->SetWait(1);
923 return;
928 GameControl* gc = core->GetGameControl();
929 if (!gc) {
930 printMessage( "GameScript","Dialog cannot be initiated because there is no GameControl.", YELLOW );
931 Sender->ReleaseCurrentAction();
932 return;
934 //can't initiate dialog, because it is already there
935 if (gc->GetDialogueFlags()&DF_IN_DIALOG) {
936 if (Flags & BD_INTERRUPT) {
937 //break the current dialog if possible
938 gc->dialoghandler->EndDialog(true);
940 //check if we could manage to break it, not all dialogs are breakable!
941 if (gc->GetDialogueFlags()&DF_IN_DIALOG) {
942 printMessage( "GameScript","Dialog cannot be initiated because there is already one.", YELLOW );
943 Sender->ReleaseCurrentAction();
944 return;
948 // starting a dialog ends cutscenes!
949 core->SetCutSceneMode(false);
951 const char* Dialog = NULL;
952 AutoTable pdtable;
954 switch (Flags & BD_LOCMASK) {
955 case BD_STRING0:
956 Dialog = parameters->string0Parameter;
957 if (Flags & BD_SETDIALOG) {
958 scr->SetDialog( Dialog );
960 break;
961 case BD_SOURCE:
962 case BD_TARGET:
963 if (swap) Dialog = scr->GetDialog();
964 else Dialog = target->GetDialog(GD_FEEDBACK);
965 break;
966 case BD_RESERVED:
967 //what if playerdialog was initiated from Player2?
968 PlayerDialogRes[5] = '1';
969 Dialog = ( const char * ) PlayerDialogRes;
970 break;
971 case BD_INTERACT: //using the source for the dialog
972 const char* scriptingname = scr->GetScriptName();
974 /* use interact.2da for short, inlined dialogue */
975 int type = CheckInteract(scriptingname, target->GetScriptName());
976 if(type) {
977 speaker->Interact(type);
978 target->Response(type);
979 Sender->ReleaseCurrentAction();
980 return;
982 /* banter dialogue */
983 pdtable.load("interdia");
984 //Dialog is a borrowed reference, we cannot free pdtable while it is being used
985 if (pdtable) {
986 Dialog = pdtable->QueryField( scriptingname, "FILE" );
988 break;
992 //dialog is not meaningful
993 if (!Dialog || Dialog[0]=='*') {
994 Sender->ReleaseCurrentAction();
995 return;
998 //maybe we should remove the action queue, but i'm unsure
999 //no, we shouldn't even call this!
1000 //Sender->ReleaseCurrentAction();
1002 // moved this here from InitDialog, because InitDialog doesn't know which side is which
1003 // post-swap (and non-actors always have IF_NOINT set) .. also added a check that it's
1004 // actually busy doing something, for the same reason
1005 if (target->GetInternalFlag()&IF_NOINT && (target->GetCurrentAction() || target->GetNextAction())) {
1006 displaymsg->DisplayConstantString(STR_TARGETBUSY,0xff0000);
1007 Sender->ReleaseCurrentAction();
1008 return;
1011 if (speaker!=target) {
1012 if (swap) {
1013 Scriptable *tmp = tar;
1014 tar = scr;
1015 scr = tmp;
1016 } else {
1017 if (!(Flags & BD_INTERRUPT)) {
1018 // added CurrentAction as part of blocking action fixes
1019 if (tar->GetCurrentAction() || tar->GetNextAction()) {
1020 displaymsg->DisplayConstantString(STR_TARGETBUSY,0xff0000);
1021 Sender->ReleaseCurrentAction();
1022 return;
1028 //don't clear target's actions, because a sequence like this will be broken:
1029 //StartDialog([PC]); SetGlobal("Talked","LOCALS",1);
1030 if (scr!=tar) {
1031 if (scr->Type==ST_ACTOR) {
1032 ((Actor *) scr)->SetOrientation(GetOrient( tar->Pos, scr->Pos), true);
1034 if (tar->Type==ST_ACTOR) {
1035 ((Actor *) tar)->SetOrientation(GetOrient( scr->Pos, tar->Pos), true);
1039 int ret;
1041 if (Dialog[0]) {
1042 //increasing NumTimesTalkedTo or NumTimesInteracted
1043 if (Flags & BD_TALKCOUNT) {
1044 gc->SetDialogueFlags(DF_TALKCOUNT, BM_OR);
1045 } else if ((Flags & BD_LOCMASK) == BD_INTERACT) {
1046 gc->SetDialogueFlags(DF_INTERACT, BM_OR);
1049 core->GetDictionary()->SetAt("DialogChoose",(ieDword) -1);
1050 ret = gc->dialoghandler->InitDialog( scr, tar, Dialog);
1052 else {
1053 ret = -1;
1056 if (ret<0) {
1057 Sender->ReleaseCurrentAction();
1058 if (Flags & BD_NOEMPTY) {
1059 return;
1061 displaymsg->DisplayConstantStringName(STR_NOTHINGTOSAY,0xff0000,tar);
1062 return;
1065 //this is a bit fishy
1066 Sender->SetWait(1);
1067 Sender->ReleaseCurrentAction();
1071 void MoveBetweenAreasCore(Actor* actor, const char *area, const Point &position, int face, bool adjust)
1073 printMessage("GameScript", " ", WHITE);
1074 printf("MoveBetweenAreas: %s to %s [%d.%d] face: %d\n", actor->GetName(0), area,position.x,position.y, face);
1075 Map* map2;
1076 Game* game = core->GetGame();
1077 if (area[0]) { //do we need to switch area?
1078 Map* map1 = actor->GetCurrentArea();
1079 //we have to change the pathfinder
1080 //to the target area if adjust==true
1081 map2 = game->GetMap(area, false);
1082 if ( map1!=map2 ) {
1083 if (map1) {
1084 map1->RemoveActor( actor );
1086 map2->AddActor( actor );
1089 actor->SetPosition(position, adjust);
1090 if (face !=-1) {
1091 actor->SetOrientation( face, false );
1093 // should this perhaps be a 'selected' check or similar instead?
1094 if (actor->InParty) {
1095 GameControl *gc=core->GetGameControl();
1096 gc->SetScreenFlags(SF_CENTERONACTOR,BM_OR);
1097 game->ChangeSong(false, true);
1101 //repeat movement, until goal isn't reached
1102 //if int0parameter is !=0, then it will try only x times
1103 void MoveToObjectCore(Scriptable *Sender, Action *parameters, ieDword flags, bool untilsee)
1105 if (Sender->Type != ST_ACTOR) {
1106 Sender->ReleaseCurrentAction();
1107 return;
1109 Scriptable* target = GetStoredActorFromObject( Sender, parameters->objects[1] );
1110 if (!target) {
1111 Sender->ReleaseCurrentAction();
1112 return;
1114 Actor* actor = ( Actor* ) Sender;
1115 if (untilsee && CanSee(actor, target, true, 0) ) {
1116 Sender->ReleaseCurrentAction();
1117 return;
1118 } else {
1119 if (PersonalDistance(actor, target)<MAX_OPERATING_DISTANCE) {
1120 Sender->ReleaseCurrentAction();
1121 return;
1124 if (!actor->InMove() || actor->Destination != target->Pos) {
1125 actor->WalkTo( target->Pos, flags, 0 );
1127 //hopefully this hack will prevent lockups
1128 if (!actor->InMove()) {
1129 Sender->ReleaseCurrentAction();
1130 return;
1133 //repeat movement...
1134 Action *newaction = ParamCopyNoOverride(parameters);
1135 if (newaction->int0Parameter!=1) {
1136 if (newaction->int0Parameter) {
1137 newaction->int0Parameter--;
1139 actor->AddActionInFront(newaction);
1140 actor->SetWait(1);
1143 Sender->ReleaseCurrentAction();
1146 void CreateItemCore(CREItem *item, const char *resref, int a, int b, int c)
1148 strncpy(item->ItemResRef, resref, 8);
1149 core->ResolveRandomItem(item);
1150 if (a==-1) {
1151 Item *origitem = gamedata->GetItem(resref);
1152 for(int i=0;i<3;i++) {
1153 ITMExtHeader *e = origitem->GetExtHeader(i);
1154 item->Usages[i]=e?e->Charges:0;
1156 gamedata->FreeItem(origitem, resref, false);
1157 } else {
1158 item->Usages[0]=(ieWord) a;
1159 item->Usages[1]=(ieWord) b;
1160 item->Usages[2]=(ieWord) c;
1162 item->Flags=0;
1165 //It is possible to attack CONTAINERS/DOORS as well!!!
1166 void AttackCore(Scriptable *Sender, Scriptable *target, int flags)
1168 //this is a dangerous cast, make sure actor is Actor * !!!
1169 Actor *actor = (Actor *) Sender;
1171 WeaponInfo wi;
1172 ITMExtHeader *header = NULL;
1173 ITMExtHeader *hittingheader = NULL;
1174 int tohit;
1175 ieDword Flags;
1176 int DamageBonus, CriticalBonus;
1177 int speed, style;
1179 //bool leftorright = (bool) ((attacksperround-attackcount)&1);
1180 bool leftorright = false;
1182 //will return false on any errors (eg, unusable weapon)
1183 if (!actor->GetCombatDetails(tohit, leftorright, wi, header, hittingheader, Flags, DamageBonus, speed, CriticalBonus, style)) {
1184 Sender->ReleaseCurrentAction();
1185 return;
1188 if (header) wi.range *= 10;
1189 else wi.range = 0;
1191 if ( target->Type == ST_DOOR || target->Type == ST_CONTAINER) {
1192 wi.range += 10;
1194 Actor *tar = NULL;
1195 ieDword targetID = 0;
1196 if (target->Type==ST_ACTOR) {
1197 tar = (Actor *) target;
1198 targetID = tar->GetID();
1200 if (actor == tar) {
1201 Sender->ReleaseCurrentAction();
1202 return;
1204 if (!(flags&AC_NO_SOUND) ) {
1205 if (actor->LastTarget != targetID) {
1206 //play attack sound for party members
1207 if (actor->InParty) {
1208 //pick from all 5 possible verbal constants
1209 actor->VerbalConstant(VB_ATTACK, 5);
1210 //DisplayStringCore(Sender, VB_ATTACK, DS_CONSOLE|DS_CONST );
1212 //display attack message
1213 displaymsg->DisplayConstantStringAction(STR_ACTION_ATTACK,0xf0f0f0, Sender, target);
1216 //action performed
1217 if(target->Type == ST_ACTOR) {
1218 actor->SetTarget( target );
1220 if ( Sender->GetCurrentArea()!=target->GetCurrentArea() ||
1221 (PersonalDistance(Sender, target) > wi.range) ) {
1222 MoveNearerTo(Sender, target, wi.range);
1223 return;
1224 } else if (target->Type == ST_DOOR) {
1225 //Forcing a lock does not launch the trap...
1226 Door* door = (Door*) target;
1227 if(door->Flags & DOOR_LOCKED) {
1228 door->TryBashLock(actor);
1230 Sender->ReleaseCurrentAction();
1231 return;
1232 } else if (target->Type == ST_CONTAINER) {
1233 Container* cont = (Container*) target;
1234 if(cont->Flags & CONT_LOCKED) {
1235 cont->TryBashLock(actor);
1237 Sender->ReleaseCurrentAction();
1238 return;
1241 actor->PerformAttack(core->GetGame()->GameTime);
1244 //we need this because some special characters like _ or * are also accepted
1245 inline bool ismysymbol(const char letter)
1247 if (letter==']') return false;
1248 if (letter=='[') return false;
1249 if (letter==')') return false;
1250 if (letter=='(') return false;
1251 if (letter=='.') return false;
1252 if (letter==',') return false;
1253 return true;
1256 //this function returns a value, symbol could be a numeric string or
1257 //a symbol from idsname
1258 static int GetIdsValue(const char *&symbol, const char *idsname)
1260 int idsfile=core->LoadSymbol(idsname);
1261 Holder<SymbolMgr> valHook = core->GetSymbol(idsfile);
1262 if (!valHook) {
1263 //FIXME:missing ids file!!!
1264 if (InDebug&ID_TRIGGERS) {
1265 printMessage("GameScript"," ",LIGHT_RED);
1266 printf("Missing IDS file %s for symbol %s!\n",idsname, symbol);
1268 return -1;
1270 char *newsymbol;
1271 int value=strtol(symbol, &newsymbol, 0);
1272 if (symbol!=newsymbol) {
1273 symbol=newsymbol;
1274 return value;
1276 char symbolname[64];
1277 int x;
1278 for (x=0;ismysymbol(*symbol) && x<(int) sizeof(symbolname)-1;x++) {
1279 symbolname[x]=*symbol;
1280 symbol++;
1282 symbolname[x]=0;
1283 return valHook->GetValue(symbolname);
1286 static void ParseIdsTarget(const char *&src, Object *&object)
1288 for (int i=0;i<ObjectFieldsCount;i++) {
1289 object->objectFields[i]=GetIdsValue(src, ObjectIDSTableNames[i]);
1290 if (*src!='.') {
1291 break;
1293 src++;
1295 src++; //skipping ]
1298 //this will skip to the next element in the prototype of an action/trigger
1299 #define SKIP_ARGUMENT() while (*str && ( *str != ',' ) && ( *str != ')' )) str++
1301 static void ParseObject(const char *&str,const char *&src, Object *&object)
1303 SKIP_ARGUMENT();
1304 object = new Object();
1305 switch (*src) {
1306 case '"':
1307 //Scriptable Name
1308 src++;
1309 int i;
1310 for (i=0;i<(int) sizeof(object->objectName)-1 && *src && *src!='"';i++)
1312 object->objectName[i] = *src;
1313 src++;
1315 object->objectName[i] = 0;
1316 src++;
1317 break;
1318 case '[':
1319 src++; //skipping [
1320 ParseIdsTarget(src, object);
1321 break;
1322 default: //nested object filters
1323 int Nesting=0;
1325 while (Nesting<MaxObjectNesting) {
1326 memmove(object->objectFilters+1, object->objectFilters, (int) sizeof(int) *(MaxObjectNesting-1) );
1327 object->objectFilters[0]=GetIdsValue(src,"object");
1328 if (*src!='(') {
1329 break;
1331 src++; //skipping (
1332 if (*src==')') {
1333 break;
1335 Nesting++;
1337 if (*src=='[') {
1338 ParseIdsTarget(src, object);
1340 src+=Nesting; //skipping )
1344 /* this function was lifted from GenerateAction, to make it clearer */
1345 Action* GenerateActionCore(const char *src, const char *str, int acIndex)
1347 Action *newAction = new Action(true);
1348 newAction->actionID = (unsigned short) actionsTable->GetValueIndex( acIndex );
1349 //this flag tells us to merge 2 consecutive strings together to get
1350 //a variable (context+variablename)
1351 int mergestrings = actionflags[newAction->actionID]&AF_MERGESTRINGS;
1352 int objectCount = ( newAction->actionID == 1 ) ? 0 : 1;
1353 int stringsCount = 0;
1354 int intCount = 0;
1355 if (actionflags[newAction->actionID]&AF_DIRECT) {
1356 Object *tmp = new Object();
1357 tmp->objectFields[0] = -1;
1358 //tmp->objectFields[1] = core->GetGameControl()->targetID;
1359 newAction->objects[objectCount++] = tmp;
1361 //Here is the Action; Now we need to evaluate the parameters, if any
1362 if (*str!=')') while (*str) {
1363 if (*(str+1)!=':') {
1364 printf("Warning, parser was sidetracked: %s\n",str);
1366 switch (*str) {
1367 default:
1368 printf("Invalid type: %s\n",str);
1369 //str++;
1370 delete newAction;
1371 return NULL;
1372 break;
1374 case 'p': //Point
1375 SKIP_ARGUMENT();
1376 src++; //Skip [
1377 newAction->pointParameter.x = (short) strtol( src, (char **) &src, 10 );
1378 src++; //Skip .
1379 newAction->pointParameter.y = (short) strtol( src, (char **) &src, 10 );
1380 src++; //Skip ]
1381 break;
1383 case 'i': //Integer
1385 //going to the variable name
1386 while (*str != '*' && *str !=',' && *str != ')' ) {
1387 str++;
1389 int value;
1390 if (*str=='*') { //there may be an IDS table
1391 str++;
1392 ieVariable idsTabName;
1393 char* tmp = idsTabName;
1394 while (( *str != ',' ) && ( *str != ')' )) {
1395 *tmp = *str;
1396 tmp++;
1397 str++;
1399 *tmp = 0;
1400 if (idsTabName[0]) {
1401 value = GetIdsValue(src, idsTabName);
1403 else {
1404 value = strtol( src, (char **) &src, 0);
1407 else { //no IDS table
1408 value = strtol( src, (char **) &src, 0);
1410 if (!intCount) {
1411 newAction->int0Parameter = value;
1412 } else if (intCount == 1) {
1413 newAction->int1Parameter = value;
1414 } else {
1415 newAction->int2Parameter = value;
1417 intCount++;
1419 break;
1421 case 'a':
1422 //Action
1424 SKIP_ARGUMENT();
1425 char action[257];
1426 int i = 0;
1427 int openParenthesisCount = 0;
1428 while (true) {
1429 if (*src == ')') {
1430 if (!openParenthesisCount)
1431 break;
1432 openParenthesisCount--;
1433 } else {
1434 if (*src == '(') {
1435 openParenthesisCount++;
1436 } else {
1437 if (( *src == ',' ) &&
1438 !openParenthesisCount)
1439 break;
1442 action[i] = *src;
1443 i++;
1444 src++;
1446 action[i] = 0;
1447 Action* act = GenerateAction( action);
1448 if (!act) {
1449 delete newAction;
1450 return NULL;
1452 act->objects[0] = newAction->objects[0];
1453 newAction->objects[0] = NULL; //avoid freeing of object
1454 delete newAction; //freeing action
1455 newAction = act;
1457 break;
1459 case 'o': //Object
1460 if (objectCount==3) {
1461 printf("Invalid object count!\n");
1462 //abort();
1463 delete newAction;
1464 return NULL;
1466 ParseObject(str, src, newAction->objects[objectCount++]);
1467 break;
1469 case 's': //String
1471 SKIP_ARGUMENT();
1472 src++;
1473 int i;
1474 char* dst;
1475 if (!stringsCount) {
1476 dst = newAction->string0Parameter;
1477 } else {
1478 dst = newAction->string1Parameter;
1480 //if there are 3 strings, the first 2 will be merged,
1481 //the last one will be left alone
1482 if (*str==')') {
1483 mergestrings = 0;
1485 //skipping the context part, which
1486 //is to be readed later
1487 if (mergestrings) {
1488 for (i=0;i<6;i++) {
1489 *dst++='*';
1492 else {
1493 i=0;
1495 while (*src != '"') {
1496 if (*src == 0) {
1497 delete newAction;
1498 return NULL;
1500 //sizeof(context+name) = 40
1501 if (i<40) {
1502 *dst++ = (char) tolower(*src);
1503 i++;
1505 src++;
1507 *dst = 0;
1508 //reading the context part
1509 if (mergestrings) {
1510 str++;
1511 if (*str!='s') {
1512 printf("Invalid mergestrings:%s\n",str);
1513 //abort();
1514 delete newAction;
1515 return NULL;
1517 SKIP_ARGUMENT();
1518 if (!stringsCount) {
1519 dst = newAction->string0Parameter;
1520 } else {
1521 dst = newAction->string1Parameter;
1524 //this works only if there are no spaces
1525 if (*src++!='"' || *src++!=',' || *src++!='"') {
1526 break;
1528 //reading the context string
1529 i=0;
1530 while (*src != '"') {
1531 if (*src == 0) {
1532 delete newAction;
1533 return NULL;
1535 if (i++<6) {
1536 *dst++ = (char) tolower(*src);
1538 src++;
1541 src++; //skipping "
1542 stringsCount++;
1544 break;
1546 str++;
1547 if (*src == ',' || *src==')')
1548 src++;
1550 return newAction;
1553 void GoNear(Scriptable *Sender, const Point &p)
1555 if (Sender->GetCurrentAction()) {
1556 printMessage("GameScript","Target busy???\n",LIGHT_RED);
1557 return;
1559 char Tmp[256];
1560 sprintf( Tmp, "MoveToPoint([%hd.%hd])", p.x, p.y );
1561 Action * action = GenerateAction( Tmp);
1562 Sender->AddActionInFront( action );
1565 void MoveNearerTo(Scriptable *Sender, Scriptable *target, int distance)
1567 Point p;
1568 Map *myarea, *hisarea;
1570 if (Sender->Type != ST_ACTOR) {
1571 printMessage("GameScript","MoveNearerTo only works with actors\n",LIGHT_RED);
1572 Sender->ReleaseCurrentAction();
1573 return;
1576 myarea = Sender->GetCurrentArea();
1577 hisarea = target->GetCurrentArea();
1578 if (hisarea!=myarea) {
1579 target = myarea->GetTileMap()->GetTravelTo(hisarea->GetScriptName());
1581 if (!target) {
1582 printMessage("GameScript", "MoveNearerTo failed to find an exit\n", YELLOW);
1583 Sender->ReleaseCurrentAction();
1584 return;
1586 ((Actor *) Sender)->UseExit(true);
1587 } else {
1588 ((Actor *) Sender)->UseExit(false);
1590 // we deliberately don't try GetLikelyPosition here for now,
1591 // maybe a future idea if we have a better implementation
1592 // (the old code used it - by passing true not 0 below - when target was a movable)
1593 GetPositionFromScriptable(target, p, 0);
1595 // account for PersonalDistance (which caller uses, but pathfinder doesn't)
1596 if (distance && Sender->Type == ST_ACTOR) {
1597 distance += ((Actor *)Sender)->size*10;
1599 if (distance && target->Type == ST_ACTOR) {
1600 distance += ((Actor *)target)->size*10;
1603 MoveNearerTo(Sender, p, distance, 0);
1606 //It is not always good to release the current action if target is unreachable
1607 //we should also raise the trigger TargetUnreachable (if this is an Attack, at least)
1608 //i hacked only this low level function, didn't need the higher ones so far
1609 int MoveNearerTo(Scriptable *Sender, const Point &p, int distance, int dont_release)
1611 if (Sender->Type != ST_ACTOR) {
1612 printMessage("GameScript","MoveNearerTo only works with actors\n",LIGHT_RED);
1613 Sender->ReleaseCurrentAction();
1614 return 0;
1617 Actor *actor = (Actor *)Sender;
1619 if (!actor->InMove() || actor->Destination != p) {
1620 actor->WalkTo(p, 0, distance);
1623 if (!actor->InMove()) {
1624 //didn't release
1625 if (dont_release) {
1626 return dont_release;
1628 // we can't walk any nearer to destination, give up
1629 Sender->ReleaseCurrentAction();
1631 return 0;
1634 void GoNearAndRetry(Scriptable *Sender, Scriptable *target, bool flag, int distance)
1636 Point p;
1637 GetPositionFromScriptable(target,p,flag);
1638 GoNearAndRetry(Sender, p, distance);
1641 void GoNearAndRetry(Scriptable *Sender, const Point &p, int distance)
1643 if (!Sender->GetCurrentAction() ) {
1644 printMessage("GameScript","NULL action retried???\n",LIGHT_RED);
1645 return;
1647 Sender->AddActionInFront( Sender->GetCurrentAction() );
1648 char Tmp[256];
1649 sprintf( Tmp, "MoveToPoint([%hd.%hd])", p.x, p.y );
1650 Action * action = GenerateAction( Tmp);
1651 //experimental hack, this value means,
1652 //MoveToPoint shall pop the next action too if movement fails
1653 //and the actor is farther than distance
1654 //this will prevent deadlocks
1655 //(we have to add 1 because otherwise distance==0 fails, we subtract it in MoveToPoint)
1656 action->int0Parameter = distance+1;
1657 Sender->AddActionInFront( action );
1660 void FreeSrc(SrcVector *poi, const ieResRef key)
1662 int res = SrcCache.DecRef((void *) poi, key, true);
1663 if (res<0) {
1664 printMessage( "GameScript", "Corrupted Src cache encountered (reference count went below zero), ", LIGHT_RED );
1665 printf( "Src name is: %.8s\n", key);
1666 abort();
1668 if (!res) {
1669 delete poi;
1673 SrcVector *LoadSrc(const ieResRef resname)
1675 SrcVector *src = (SrcVector *) SrcCache.GetResource(resname);
1676 if (src) {
1677 return src;
1679 DataStream* str = gamedata->GetResource( resname, IE_SRC_CLASS_ID );
1680 if ( !str) {
1681 return NULL;
1683 ieDword size=0;
1684 str->ReadDword(&size);
1685 src = new SrcVector(size);
1686 SrcCache.SetAt( resname, (void *) src );
1687 while (size--) {
1688 ieDword tmp;
1689 str->ReadDword(&tmp);
1690 src->at(size)=tmp;
1691 str->ReadDword(&tmp);
1693 delete ( str );
1694 return src;
1697 #define MEMCPY(a,b) memcpy((a),(b),sizeof(a) )
1699 static Object *ObjectCopy(Object *object)
1701 if (!object) return NULL;
1702 Object *newObject = new Object();
1703 MEMCPY( newObject->objectFields, object->objectFields );
1704 MEMCPY( newObject->objectFilters, object->objectFilters );
1705 MEMCPY( newObject->objectRect, object->objectRect );
1706 MEMCPY( newObject->objectName, object->objectName );
1707 return newObject;
1710 Action *ParamCopy(Action *parameters)
1712 Action *newAction = new Action(true);
1713 newAction->actionID = parameters->actionID;
1714 newAction->int0Parameter = parameters->int0Parameter;
1715 newAction->int1Parameter = parameters->int1Parameter;
1716 newAction->int2Parameter = parameters->int2Parameter;
1717 newAction->pointParameter = parameters->pointParameter;
1718 MEMCPY( newAction->string0Parameter, parameters->string0Parameter );
1719 MEMCPY( newAction->string1Parameter, parameters->string1Parameter );
1720 for (int c=0;c<3;c++) {
1721 newAction->objects[c]= ObjectCopy( parameters->objects[c] );
1723 return newAction;
1726 Action *ParamCopyNoOverride(Action *parameters)
1728 Action *newAction = new Action(true);
1729 newAction->actionID = parameters->actionID;
1730 newAction->int0Parameter = parameters->int0Parameter;
1731 newAction->int1Parameter = parameters->int1Parameter;
1732 newAction->int2Parameter = parameters->int2Parameter;
1733 newAction->pointParameter = parameters->pointParameter;
1734 MEMCPY( newAction->string0Parameter, parameters->string0Parameter );
1735 MEMCPY( newAction->string1Parameter, parameters->string1Parameter );
1736 newAction->objects[0]= NULL;
1737 newAction->objects[1]= ObjectCopy( parameters->objects[1] );
1738 newAction->objects[2]= ObjectCopy( parameters->objects[2] );
1739 return newAction;
1742 Trigger *GenerateTriggerCore(const char *src, const char *str, int trIndex, int negate)
1744 Trigger *newTrigger = new Trigger();
1745 newTrigger->triggerID = (unsigned short) triggersTable->GetValueIndex( trIndex )&0x3fff;
1746 newTrigger->flags = (unsigned short) negate;
1747 int mergestrings = triggerflags[newTrigger->triggerID]&TF_MERGESTRINGS;
1748 int stringsCount = 0;
1749 int intCount = 0;
1750 //Here is the Trigger; Now we need to evaluate the parameters
1751 if (*str!=')') while (*str) {
1752 if (*(str+1)!=':') {
1753 printf("Warning, parser was sidetracked: %s\n",str);
1755 switch (*str) {
1756 default:
1757 printf("Invalid type: %s\n",str);
1758 //str++;
1759 delete newTrigger;
1760 return NULL;
1761 break;
1763 case 'p': //Point
1764 SKIP_ARGUMENT();
1765 src++; //Skip [
1766 newTrigger->pointParameter.x = (short) strtol( src, (char **) &src, 10 );
1767 src++; //Skip .
1768 newTrigger->pointParameter.y = (short) strtol( src, (char **) &src, 10 );
1769 src++; //Skip ]
1770 break;
1772 case 'i': //Integer
1774 //going to the variable name
1775 while (*str != '*' && *str !=',' && *str != ')' ) {
1776 str++;
1778 int value;
1779 if (*str=='*') { //there may be an IDS table
1780 str++;
1781 ieVariable idsTabName;
1782 char* tmp = idsTabName;
1783 while (( *str != ',' ) && ( *str != ')' )) {
1784 *tmp = *str;
1785 tmp++;
1786 str++;
1788 *tmp = 0;
1789 if (idsTabName[0]) {
1790 value = GetIdsValue(src, idsTabName);
1792 else {
1793 value = strtol( src, (char **) &src, 0);
1796 else { //no IDS table
1797 value = strtol( src, (char **) &src, 0);
1799 if (!intCount) {
1800 newTrigger->int0Parameter = value;
1801 } else if (intCount == 1) {
1802 newTrigger->int1Parameter = value;
1803 } else {
1804 newTrigger->int2Parameter = value;
1806 intCount++;
1808 break;
1810 case 'o': //Object
1811 ParseObject(str, src, newTrigger->objectParameter);
1812 break;
1814 case 's': //String
1816 SKIP_ARGUMENT();
1817 src++;
1818 int i;
1819 char* dst;
1820 if (!stringsCount) {
1821 dst = newTrigger->string0Parameter;
1822 } else {
1823 dst = newTrigger->string1Parameter;
1825 //skipping the context part, which
1826 //is to be readed later
1827 if (mergestrings) {
1828 for (i=0;i<6;i++) {
1829 *dst++='*';
1832 else {
1833 i=0;
1835 while (*src != '"') {
1836 if (*src == 0) {
1837 delete newTrigger;
1838 return NULL;
1841 //sizeof(context+name) = 40
1842 if (i<40) {
1843 *dst++ = (char) tolower(*src);
1844 i++;
1846 src++;
1848 *dst = 0;
1849 //reading the context part
1850 if (mergestrings) {
1851 str++;
1852 if (*str!='s') {
1853 printf("Invalid mergestrings:%s\n",str);
1854 //abort();
1855 delete newTrigger;
1856 return NULL;
1858 SKIP_ARGUMENT();
1859 if (!stringsCount) {
1860 dst = newTrigger->string0Parameter;
1861 } else {
1862 dst = newTrigger->string1Parameter;
1865 //this works only if there are no spaces
1866 if (*src++!='"' || *src++!=',' || *src++!='"') {
1867 break;
1869 //reading the context string
1870 i=0;
1871 while (*src != '"') {
1872 if (*src == 0) {
1873 delete newTrigger;
1874 return NULL;
1877 if (i++<6) {
1878 *dst++ = (char) tolower(*src);
1880 src++;
1883 src++; //skipping "
1884 stringsCount++;
1886 break;
1888 str++;
1889 if (*src == ',' || *src==')')
1890 src++;
1892 return newTrigger;
1895 void SetVariable(Scriptable* Sender, const char* VarName, const char* Context, ieDword value)
1897 char newVarName[8+33];
1899 if (InDebug&ID_VARIABLES) {
1900 printf( "Setting variable(\"%s%s\", %d)\n", Context,
1901 VarName, value );
1903 strncpy( newVarName, Context, 6 );
1904 newVarName[6]=0;
1905 if (strnicmp( newVarName, "MYAREA", 6 ) == 0) {
1906 Sender->GetCurrentArea()->locals->SetAt( VarName, value );
1907 return;
1909 if (strnicmp( newVarName, "LOCALS", 6 ) == 0) {
1910 Sender->locals->SetAt( VarName, value );
1911 return;
1913 Game *game = core->GetGame();
1914 if (!strnicmp(newVarName,"KAPUTZ",6) && core->HasFeature(GF_HAS_KAPUTZ) ) {
1915 game->kaputz->SetAt( VarName, value );
1916 return;
1919 if (strnicmp(newVarName,"GLOBAL",6) ) {
1920 Map *map=game->GetMap(game->FindMap(newVarName));
1921 if (map) {
1922 map->locals->SetAt( VarName, value);
1924 else if (InDebug&ID_VARIABLES) {
1925 printMessage("GameScript"," ",YELLOW);
1926 printf("Invalid variable %s %s in setvariable\n",Context, VarName);
1929 else {
1930 game->locals->SetAt( VarName, ( ieDword ) value );
1934 void SetVariable(Scriptable* Sender, const char* VarName, ieDword value)
1936 char newVarName[8];
1937 const char *poi;
1939 poi = &VarName[6];
1940 //some HoW triggers use a : to separate the scope from the variable name
1941 if (*poi==':') {
1942 poi++;
1945 if (InDebug&ID_VARIABLES) {
1946 printf( "Setting variable(\"%s\", %d)\n", VarName, value );
1948 strncpy( newVarName, VarName, 6 );
1949 newVarName[6]=0;
1950 if (strnicmp( newVarName, "MYAREA", 6 ) == 0) {
1951 Sender->GetCurrentArea()->locals->SetAt( poi, value );
1952 return;
1954 if (strnicmp( newVarName, "LOCALS", 6 ) == 0) {
1955 Sender->locals->SetAt( poi, value );
1956 return;
1958 Game *game = core->GetGame();
1959 if (!strnicmp(newVarName,"KAPUTZ",6) && core->HasFeature(GF_HAS_KAPUTZ) ) {
1960 game->kaputz->SetAt( poi, value );
1961 return;
1963 if (strnicmp(newVarName,"GLOBAL",6) ) {
1964 Map *map=game->GetMap(game->FindMap(newVarName));
1965 if (map) {
1966 map->locals->SetAt( poi, value);
1968 else if (InDebug&ID_VARIABLES) {
1969 printMessage("GameScript"," ",YELLOW);
1970 printf("Invalid variable %s in setvariable\n",VarName);
1973 else {
1974 game->locals->SetAt( poi, ( ieDword ) value );
1978 ieDword CheckVariable(Scriptable* Sender, const char* VarName, bool *valid)
1980 char newVarName[8];
1981 const char *poi;
1982 ieDword value = 0;
1984 strncpy( newVarName, VarName, 6 );
1985 newVarName[6]=0;
1986 poi = &VarName[6];
1987 //some HoW triggers use a : to separate the scope from the variable name
1988 if (*poi==':') {
1989 poi++;
1992 if (strnicmp( newVarName, "MYAREA", 6 ) == 0) {
1993 Sender->GetCurrentArea()->locals->Lookup( poi, value );
1994 if (InDebug&ID_VARIABLES) {
1995 printf("CheckVariable %s: %d\n",VarName, value);
1997 return value;
1999 if (strnicmp( newVarName, "LOCALS", 6 ) == 0) {
2000 Sender->locals->Lookup( poi, value );
2001 if (InDebug&ID_VARIABLES) {
2002 printf("CheckVariable %s: %d\n",VarName, value);
2004 return value;
2006 Game *game = core->GetGame();
2007 if (!strnicmp(newVarName,"KAPUTZ",6) && core->HasFeature(GF_HAS_KAPUTZ) ) {
2008 game->kaputz->Lookup( poi, value );
2009 if (InDebug&ID_VARIABLES) {
2010 printf("CheckVariable %s: %d\n",VarName, value);
2012 return value;
2014 if (strnicmp(newVarName,"GLOBAL",6) ) {
2015 Map *map=game->GetMap(game->FindMap(newVarName));
2016 if (map) {
2017 map->locals->Lookup( poi, value);
2018 } else {
2019 if (valid) {
2020 *valid=false;
2022 if (InDebug&ID_VARIABLES) {
2023 printMessage("GameScript"," ",YELLOW);
2024 printf("Invalid variable %s in checkvariable\n",VarName);
2027 } else {
2028 game->locals->Lookup( poi, value );
2030 if (InDebug&ID_VARIABLES) {
2031 printf("CheckVariable %s: %d\n",VarName, value);
2033 return value;
2036 ieDword CheckVariable(Scriptable* Sender, const char* VarName, const char* Context, bool *valid)
2038 char newVarName[8];
2039 ieDword value = 0;
2041 strncpy(newVarName, Context, 6);
2042 newVarName[6]=0;
2043 if (strnicmp( newVarName, "MYAREA", 6 ) == 0) {
2044 Sender->GetCurrentArea()->locals->Lookup( VarName, value );
2045 if (InDebug&ID_VARIABLES) {
2046 printf("CheckVariable %s%s: %d\n",Context, VarName, value);
2048 return value;
2050 if (strnicmp( newVarName, "LOCALS", 6 ) == 0) {
2051 Sender->locals->Lookup( VarName, value );
2052 if (InDebug&ID_VARIABLES) {
2053 printf("CheckVariable %s%s: %d\n",Context, VarName, value);
2055 return value;
2057 Game *game = core->GetGame();
2058 if (!strnicmp(newVarName,"KAPUTZ",6) && core->HasFeature(GF_HAS_KAPUTZ) ) {
2059 game->kaputz->Lookup( VarName, value );
2060 if (InDebug&ID_VARIABLES) {
2061 printf("CheckVariable %s%s: %d\n",Context, VarName, value);
2063 return value;
2065 if (strnicmp(newVarName,"GLOBAL",6) ) {
2066 Map *map=game->GetMap(game->FindMap(newVarName));
2067 if (map) {
2068 map->locals->Lookup( VarName, value);
2069 } else {
2070 if (valid) {
2071 *valid=false;
2073 if (InDebug&ID_VARIABLES) {
2074 printMessage("GameScript"," ",YELLOW);
2075 printf("Invalid variable %s %s in checkvariable\n",Context, VarName);
2078 } else {
2079 game->locals->Lookup( VarName, value );
2081 if (InDebug&ID_VARIABLES) {
2082 printf("CheckVariable %s%s: %d\n",Context, VarName, value);
2084 return value;
2087 int DiffCore(ieDword a, ieDword b, int diffmode)
2089 switch (diffmode) {
2090 case LESS_THAN:
2091 if (a<b) {
2092 return 1;
2094 break;
2095 case EQUALS:
2096 if (a==b) {
2097 return 1;
2099 break;
2100 case GREATER_THAN:
2101 if (a>b) {
2102 return 1;
2104 break;
2105 case GREATER_OR_EQUALS:
2106 if (a>=b) {
2107 return 1;
2109 break;
2110 case NOT_EQUALS:
2111 if (a!=b) {
2112 return 1;
2114 break;
2115 case BINARY_LESS_OR_EQUALS:
2116 if ((a&b) == a) {
2117 return 1;
2119 break;
2120 case BINARY_MORE:
2121 if ((a&b) != a) {
2122 return 1;
2124 break;
2125 case BINARY_MORE_OR_EQUALS:
2126 if ((a&b) == b) {
2127 return 1;
2129 break;
2130 case BINARY_LESS:
2131 if ((a&b) != b) {
2132 return 1;
2134 break;
2135 case BINARY_INTERSECT:
2136 if (a&b) {
2137 return 1;
2139 break;
2140 case BINARY_NOT_INTERSECT:
2141 if (!(a&b)) {
2142 return 1;
2144 break;
2145 default: //less or equals
2146 if (a<=b) {
2147 return 1;
2149 break;
2151 return 0;
2154 int GetGroup(Actor *actor)
2156 int type = 2; //neutral, has no enemies
2157 if (actor->GetStat(IE_EA) <= EA_GOODCUTOFF) {
2158 type = 1; //PC
2160 if (actor->GetStat(IE_EA) >= EA_EVILCUTOFF) {
2161 type = 0;
2163 return type;
2166 Point GetEntryPoint(const char *areaname, const char *entryname)
2168 Point p;
2170 AutoTable tab("entries");
2171 if (!tab) {
2172 return p;
2174 const char *tmpstr = tab->QueryField(areaname, entryname);
2175 int x=-1;
2176 int y=-1;
2177 sscanf(tmpstr, "%d.%d", &x, &y);
2178 p.x=(short) x;
2179 p.y=(short) y;
2180 return p;
2183 /* returns a spell's casting distance, it depends on the caster */
2184 unsigned int GetSpellDistance(ieResRef spellres, Actor *actor)
2186 unsigned int dist;
2188 Spell* spl = gamedata->GetSpell( spellres );
2189 if (!spl) {
2190 printMessage("GameScript"," ",LIGHT_RED);
2191 printf("Spell couldn't be found:%.8s.\n", spellres);
2192 return 0;
2194 dist=spl->GetCastingDistance(actor);
2195 gamedata->FreeSpell(spl, spellres, false);
2196 return dist*15;
2199 /* returns a spell's casting distance, it depends on the caster */
2200 unsigned int GetItemDistance(ieResRef itemres, int header)
2202 unsigned int dist;
2204 Item* itm = gamedata->GetItem( itemres );
2205 if (!itm) {
2206 printMessage("GameScript"," ",LIGHT_RED);
2207 printf("Item couldn't be found:%.8s.\n", itemres);
2208 return 0;
2210 dist=itm->GetCastingDistance(header);
2211 gamedata->FreeItem(itm, itemres, false);
2212 return dist*15;
2215 //read the wish 2da
2216 void SetupWishCore(Scriptable *Sender, int column, int picks)
2218 int count;
2219 ieVariable varname;
2220 int *selects;
2221 int i,j;
2223 AutoTable tm("wish");
2224 if (!tm) {
2225 printStatus( "ERROR", LIGHT_RED );
2226 printf( "Cannot find wish.2da.\n");
2227 return;
2230 selects = (int *) malloc(picks*sizeof(int));
2231 count = tm->GetRowCount();
2233 for(i=0;i<99;i++) {
2234 snprintf(varname,32, "wishpower%02d", i);
2235 if(CheckVariable(Sender, varname, "GLOBAL") ) {
2236 SetVariable(Sender, varname, "GLOBAL", 0);
2240 if (count<picks) {
2241 for(i=0;i<count;i++) {
2242 selects[i]=i;
2244 while(i++<picks) {
2245 selects[i]=-1;
2247 } else {
2248 for(i=0;i<picks;i++) {
2249 selects[i]=rand()%count;
2250 retry:
2251 for(j=0;j<i;j++) {
2252 if(selects[i]==selects[j]) {
2253 selects[i]++;
2254 goto retry;
2260 for (i = 0; i < picks; i++) {
2261 if (selects[i]<0)
2262 continue;
2263 int spnum = atoi( tm->QueryField( selects[i]-1, column-1 ) );
2264 snprintf(varname,32,"wishpower%02d", spnum);
2265 SetVariable(Sender, varname, "GLOBAL",1);
2267 free(selects);
2270 #define MAX_ISLAND_POLYGONS 10
2272 //read a polygon 2da
2273 Gem_Polygon *GetPolygon2DA(ieDword index)
2275 ieResRef resref;
2277 if (index>=MAX_ISLAND_POLYGONS) {
2278 return NULL;
2281 if (!polygons) {
2282 polygons = (Gem_Polygon **) calloc(MAX_ISLAND_POLYGONS, sizeof(Gem_Polygon *) );
2284 if (polygons[index]) {
2285 return polygons[index];
2287 snprintf(resref, sizeof(ieResRef), "ISLAND%02d", index);
2288 AutoTable tm(resref);
2289 if (!tm) {
2290 return NULL;
2292 int cnt = tm->GetRowCount();
2293 if (!cnt) {
2294 return NULL;
2296 Point *p = new Point[cnt];
2298 int i = cnt;
2299 while(i--) {
2300 p[i].x = atoi(tm->QueryField(i, 0));
2301 p[i].y = atoi(tm->QueryField(i, 1));
2304 polygons[index] = new Gem_Polygon(p, cnt, NULL);
2305 delete [] p;
2306 return polygons[index];