Initial Comit: First commit.
[SauerEngine.git] / src / rpggame / rpgobj.h
blobff9b1b83f3a8704f9717cd62a03158ac44fb171c
1 struct rpgobjset;
3 struct rpgquest
5 rpgquest *next;
6 const char *npc;
7 const char *questline;
8 bool completed;
10 rpgquest(rpgquest *_n, const char *_npc, const char *_ql) : next(_n), npc(_npc), questline(_ql), completed(false) {}
13 struct rpgaction
15 rpgaction *next;
16 char *initiate, *script;
17 rpgquest *q;
18 bool used;
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)
26 if(!*script) return;
27 os.pushobj(user);
28 os.pushobj(target);
29 os.pushobj(obj);
30 execute(script);
31 used = true;
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
46 enum
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
53 enum
55 MENU_DEFAULT,
56 MENU_BUY,
57 MENU_SELL
60 int itemflags;
62 rpgaction *actions, action_use;
63 char *abovetext;
65 int menutime, menutab, menuwhich;
67 rpgobjset &os;
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) {}
75 ~rpgobj()
77 DELETEP(inventory);
78 DELETEP(sibling);
79 DELETEP(ent);
80 DELETEP(actions);
83 void scriptinit()
85 DELETEP(inventory);
86 s_sprintfd(aliasname)("spawn_%s", name);
87 execute(aliasname);
90 void decontain()
92 if(parent) parent->remove(this);
95 void add(rpgobj *o, int itemflags)
97 o->sibling = inventory;
98 o->parent = this;
99 inventory = o;
100 o->itemflags = itemflags;
102 if(itemflags&IF_INVENTORY) recalcstats();
105 void remove(rpgobj *o)
107 for(rpgobj **l = &inventory; *l; )
108 if(*l==o)
110 *l = o->sibling;
111 o->sibling = o->parent = NULL;
113 else l = &(*l)->sibling;
115 if(o->itemflags&IF_INVENTORY) recalcstats();
118 void recalcstats()
120 st_reset();
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; };
128 return *this;
131 void placeinworld(rpgent *_ent)
133 if(!model) model = "tentus/moneybag";
134 ent = _ent;
135 setbbfrommodel(ent, model);
136 entinmap(ent);
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));
138 st_init();
141 void render()
143 if(s_ai)
145 float sink = 0;
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);
154 else
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);
177 void droploot()
179 loopinventorytype(IF_LOOT)
181 o->decontain();
182 os.pushobj(o);
183 os.placeinworld(ent->o, rnd(360));
184 droploot();
185 return;
189 rpgobj *take(char *name)
191 loopinventory() if(strcmp(o->name, name)==0)
193 o->decontain();
194 return o;
196 return NULL;
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)
209 s_hp = 0;
210 ent->state = CS_DEAD;
211 ent->attacking = false;
212 ent->lastaction = os.cl.lastmillis;
213 menutime = 0;
214 conoutf("%s killed: %s", attacker.name, name);
215 droploot();
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;
228 o.usesound(ent);
229 return true;
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);
241 void selectuse()
243 if(s_usetype)
245 conoutf("\f2using: %s", name);
246 os.selected = this;
248 else
250 useaction(*os.playerobj, *os.playerobj->ent, true);
254 void guiaction(g3d_gui &g, rpgaction *a)
256 if(!a) return;
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);
269 switch(menuwhich)
271 case MENU_DEFAULT:
273 g.tab(name, 0xFFFFFF);
274 if(abovetext) g.text(abovetext, 0xDDFFDD);
276 guiaction(g, actions);
278 if(s_ai)
280 bool trader = false;
281 loopinventorytype(IF_TRADE) trader = true;
282 if(trader)
284 if(g.button("buy", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_BUY;
285 if(g.button("sell", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_SELL;
288 else
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)
299 selectuse();
302 break;
305 case MENU_BUY:
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");
314 if(ret&G3D_UP)
316 if(os.playerobj->s_gold>=price)
318 conoutf("\f2you bought %s for %d gold", o->name, price);
319 os.playerobj->s_gold -= price;
320 s_gold += price;
321 o->decontain();
322 os.playerobj->add(o, IF_INVENTORY);
324 else
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;
333 break;
336 case MENU_SELL:
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;
342 break;
345 g.end();
348 void invgui(g3d_gui &g, rpgobj *buyer = NULL)
350 loopinventory()
352 int price = o->s_worth/2;
353 s_sprintfd(info)("%s (%d)", o->name, price);
354 int ret = g.button(info, 0xFFFFFF, "coins");
355 if(ret&G3D_UP)
357 if(buyer)
359 if(price>buyer->s_gold)
361 conoutf("\f2%s cannot afford to buy %s from you!", buyer->name, o->name);
363 else
365 if(price)
367 conoutf("\f2you sold %s for %d gold", o->name, price);
368 s_gold += price;
369 buyer->s_gold -= price;
370 o->decontain();
371 buyer->add(o, IF_TRADE);
373 else
375 conoutf("\f2you cannot sell %s", o->name);
379 else // player wants to use this item
381 o->selectuse();
385 s_sprintfd(info)("you have %d gold", os.playerobj->s_gold);
386 g.text(info, 0xAAAAAA, "info");
389 void g3d_menu()
391 if(!menutime) return;
392 g3d_addgui(this, vec(ent->o).add(vec(0, 0, 2)));