10 rpgquest(rpgquest
*_n
, const char *_npc
, const char *_ql
) : next(_n
), npc(_npc
), questline(_ql
), completed(false) {}
16 char *initiate
, *script
;
20 rpgaction(char *_i
= NULL
, char *_s
= NULL
, rpgaction
*_n
= NULL
) : next(_n
), initiate(_i
), script(_s
), q(NULL
), used(false) {}
22 ~rpgaction() { DELETEP(next
); }
24 void exec(rpgobj
*obj
, rpgobj
*target
, rpgobj
*user
, rpgobjset
&os
)
35 struct rpgobj
: g3d_callback
, stats
37 rpgobj
*parent
; // container object, if not top level
38 rpgobj
*inventory
; // contained objects, if any
39 rpgobj
*sibling
; // used for linking, if contained
41 rpgent
*ent
; // representation in the world, if top level
43 const char *name
; // name it was spawned as
44 const char *model
; // what to display it as
48 IF_INVENTORY
= 1, // parent owns this object, will contribute to parent stats
49 IF_LOOT
= 2, // if parent dies, this object should drop to the ground
50 IF_TRADE
= 4, // parent has this item available for trade, for player, all currently unused weapons etc are of this type
62 rpgaction
*actions
, action_use
;
65 int menutime
, menutab
, menuwhich
;
69 #define loopinventory() for(rpgobj *o = inventory; o; o = o->sibling)
70 #define loopinventorytype(T) loopinventory() if(o->itemflags&(T))
72 rpgobj(const char *_name
, rpgobjset
&_os
) : parent(NULL
), inventory(NULL
), sibling(NULL
), ent(NULL
), name(_name
), model(NULL
), itemflags(IF_INVENTORY
),
73 actions(NULL
), abovetext(NULL
), menutime(0), menutab(1), menuwhich(MENU_DEFAULT
), os(_os
) {}
86 s_sprintfd(aliasname
)("spawn_%s", name
);
92 if(parent
) parent
->remove(this);
95 void add(rpgobj
*o
, int itemflags
)
97 o
->sibling
= inventory
;
100 o
->itemflags
= itemflags
;
102 if(itemflags
&IF_INVENTORY
) recalcstats();
105 void remove(rpgobj
*o
)
107 for(rpgobj
**l
= &inventory
; *l
; )
111 o
->sibling
= o
->parent
= NULL
;
113 else l
= &(*l
)->sibling
;
115 if(o
->itemflags
&IF_INVENTORY
) recalcstats();
121 loopinventorytype(IF_INVENTORY
) st_accumulate(*o
);
124 rpgobj
&selectedweapon()
126 if(this==os
.playerobj
) return os
.selected
? *os
.selected
: *this;
127 else { loopinventorytype(IF_INVENTORY
) if(o
->s_usetype
) return *o
; };
131 void placeinworld(rpgent
*_ent
)
133 if(!model
) model
= "tentus/moneybag";
135 setbbfrommodel(ent
, model
);
137 //ASSERT(!(ent->o.x<0 || ent->o.y<0 || ent->o.z<0 || ent->o.x>4096 || ent->o.y>4096 || ent->o.z>4096));
146 if(ent
->physstate
>=PHYS_SLIDE
)
147 sink
= raycube(ent
->o
, vec(0, 0, -1), 2*ent
->eyeheight
)-ent
->eyeheight
;
148 ent
->sink
= ent
->sink
*0.8 + sink
*0.2;
149 //if(ent->blocked) particle_splash(0, 100, 100, ent->o);
150 renderclient(ent
, model
, NULL
, ANIM_PUNCH
, 300, ent
->lastaction
, 0, ent
->sink
);
151 if(s_hp
<eff_maxhp() && ent
->state
==CS_ALIVE
) particle_meter(ent
->abovehead(), s_hp
/(float)eff_maxhp(), 17);
156 rendermodel(NULL
, model
, ANIM_MAPMODEL
|ANIM_LOOP
, vec(ent
->o
).sub(vec(0, 0, ent
->eyeheight
)), ent
->yaw
+90, 0, MDL_CULL_VFC
| MDL_CULL_DIST
| MDL_CULL_OCCLUDED
| MDL_LIGHT
, ent
);
160 void update(int curtime
)
162 float dist
= ent
->o
.dist(os
.cl
.player1
.o
);
163 if(s_ai
) { ent
->update(curtime
, dist
); st_update(ent
->cl
.lastmillis
); };
164 moveplayer(ent
, 10, false, curtime
); // 10 or above gets blocked less, because physics accuracy doesn't need extra tests
165 //ASSERT(!(ent->o.x<0 || ent->o.y<0 || ent->o.z<0 || ent->o.x>4096 || ent->o.y>4096 || ent->o.z>4096));
166 if(!menutime
&& dist
<(s_ai
? 40 : 24) && ent
->state
==CS_ALIVE
&& s_ai
<2) { menutime
= starttime(); menuwhich
= MENU_DEFAULT
; }
167 else if(dist
>(s_ai
? 96 : 48)) menutime
= 0;
170 void addaction(char *initiate
, char *script
, bool startquest
)
172 for(rpgaction
*a
= actions
; a
; a
= a
->next
) if(strcmp(a
->initiate
, initiate
)==0) return;
173 actions
= new rpgaction(initiate
, script
, actions
);
174 if(startquest
) os
.addquest(actions
, abovetext
, name
);
179 loopinventorytype(IF_LOOT
)
183 os
.placeinworld(ent
->o
, rnd(360));
189 rpgobj
*take(char *name
)
191 loopinventory() if(strcmp(o
->name
, name
)==0)
199 void takedamage(int damage
, rpgobj
&attacker
)
201 ent
->enemy
= attacker
.ent
;
203 particle_splash(3, damage
*5, 1000, ent
->o
);
204 s_sprintfd(ds
)("@%d", damage
);
205 particle_text(ent
->o
, ds
, 8);
207 if((s_hp
-= damage
)<=0)
210 ent
->state
= CS_DEAD
;
211 ent
->attacking
= false;
212 ent
->lastaction
= os
.cl
.lastmillis
;
214 conoutf("%s killed: %s", attacker
.name
, name
);
219 void usesound(rpgent
*user
)
221 if(s_usesound
) playsound(s_usesound
, &user
->o
);
224 bool usemana(rpgobj
&o
)
226 if(o
.s_manacost
>s_mana
) { if(this==os
.playerobj
) conoutf("\f2not enough mana"); return false; };
227 s_mana
-= o
.s_manacost
;
232 void useaction(rpgobj
&target
, rpgent
&initiator
, bool chargemana
)
234 if(action_use
.script
&& (!chargemana
|| initiator
.ro
->usemana(*this)))
236 action_use
.exec(this, &target
, initiator
.ro
, os
);
237 if(s_useamount
&& !--s_useamount
) os
.removefromsystem(this);
245 conoutf("\f2using: %s", name
);
250 useaction(*os
.playerobj
, *os
.playerobj
->ent
, true);
254 void guiaction(g3d_gui
&g
, rpgaction
*a
)
257 guiaction(g
, a
->next
);
258 if(g
.button(a
->initiate
, a
->used
? 0xAAAAAA : 0xFFFFFF, "chat")&G3D_UP
)
260 os
.currentquest
= a
->q
;
261 a
->exec(this, os
.playerobj
, os
.playerobj
, os
);
262 os
.currentquest
= NULL
;
266 void gui(g3d_gui
&g
, bool firstpass
)
268 g
.start(menutime
, 0.015f
, &menutab
);
273 g
.tab(name
, 0xFFFFFF);
274 if(abovetext
) g
.text(abovetext
, 0xDDFFDD);
276 guiaction(g
, actions
);
281 loopinventorytype(IF_TRADE
) trader
= true;
284 if(g
.button("buy", 0xFFFFFF, "coins")&G3D_UP
) menuwhich
= MENU_BUY
;
285 if(g
.button("sell", 0xFFFFFF, "coins")&G3D_UP
) menuwhich
= MENU_SELL
;
290 s_sprintfd(wtext
)("worth %d", s_worth
);
291 g
.text(wtext
, 0xAAAAAA, "coins");
292 if(g
.button("take", 0xFFFFFF, "hand")&G3D_UP
)
294 conoutf("\f2you take a %s (worth %d gold)", name
, s_worth
);
295 os
.take(this, os
.playerobj
);
297 if(!s_usetype
&& g
.button("use", 0xFFFFFF, "hand")&G3D_UP
)
307 s_sprintfd(info
)("buying from: %s", name
);
308 g
.tab(info
, 0xFFFFFF);
309 loopinventorytype(IF_TRADE
)
311 int price
= o
->s_worth
;
312 s_sprintfd(info
)("%s (%d)", o
->name
, price
);
313 int ret
= g
.button(info
, 0xFFFFFF, "coins");
316 if(os
.playerobj
->s_gold
>=price
)
318 conoutf("\f2you bought %s for %d gold", o
->name
, price
);
319 os
.playerobj
->s_gold
-= price
;
322 os
.playerobj
->add(o
, IF_INVENTORY
);
326 conoutf("\f2you cannot afford this item!");
330 s_sprintf(info
)("you have %d gold", os
.playerobj
->s_gold
);
331 g
.text(info
, 0xAAAAAA, "info");
332 if(g
.button("done buying", 0xFFFFFF, "coins")&G3D_UP
) menuwhich
= MENU_DEFAULT
;
338 s_sprintfd(info
)("selling to: %s", name
);
339 g
.tab(info
, 0xFFFFFF);
340 os
.playerobj
->invgui(g
, this);
341 if(g
.button("done selling", 0xFFFFFF, "coins")&G3D_UP
) menuwhich
= MENU_DEFAULT
;
348 void invgui(g3d_gui
&g
, rpgobj
*buyer
= NULL
)
352 int price
= o
->s_worth
/2;
353 s_sprintfd(info
)("%s (%d)", o
->name
, price
);
354 int ret
= g
.button(info
, 0xFFFFFF, "coins");
359 if(price
>buyer
->s_gold
)
361 conoutf("\f2%s cannot afford to buy %s from you!", buyer
->name
, o
->name
);
367 conoutf("\f2you sold %s for %d gold", o
->name
, price
);
369 buyer
->s_gold
-= price
;
371 buyer
->add(o
, IF_TRADE
);
375 conoutf("\f2you cannot sell %s", o
->name
);
379 else // player wants to use this item
385 s_sprintfd(info
)("you have %d gold", os
.playerobj
->s_gold
);
386 g
.text(info
, 0xAAAAAA, "info");
391 if(!menutime
) return;
392 g3d_addgui(this, vec(ent
->o
).add(vec(0, 0, 2)));