#define _POSIX_C_SOURCE 200809L #define _XOPEN_SOURCE_EXTENDED #include #include #include #include #include #include #include #include #include #include #include #include #define NOOBJ_OBJECT (struct object){.type = NOOBJ, .symbol = ' '} unsigned m_id = 0; struct choice get_input(void) { struct choice c; start:; int res = getch(); assert(res != ERR); switch(res) { case 'w': c.a = MOVE; c.u.d = UP; break; case 'a': c.a = MOVE; c.u.d = LEFT; break; case 's': c.a = MOVE; c.u.d = DOWN; break; case 'd': c.a = MOVE; c.u.d = RIGHT; break; case 'f': c.a = SPAWN; break; case 'q': c.a = QUIT; break; case 'i': c.a = INVENTORY; break; case KEY_RESIZE: c.a = REDRAW; break; case KEY_UP: log_scroll(UP); goto start; case KEY_DOWN: log_scroll(DOWN); goto start; default: c.a = INVALID; } return c; } static void addxp(struct monster *m, unsigned xp) { m->xp += xp; if(m->xp >= m->level *100) { m->xp -= m->level*100; m->level++; m->dmg++; m->max_hp += 10; m->current_hp += 10; const char *str = "!You leveled up!"; messagebox(str, 1, strlen(str)); } } static void attack(struct object *src, struct object *target) { assert(src->type == MONSTER); if(target->type == MONSTER) { if(src->u.m.isplayer) logstr("%s hit %s for %d damage", src->u.m.name, target->u.m.name, src->u.m.dmg); if(src->u.m.dmg >= target->u.m.current_hp) { logstr("%s killed %s", src->u.m.name ,target->u.m.name); if(src->u.m.isplayer) { addxp(&src->u.m, target->u.m.level*10); } else { messagebox("U done did dedden", 1, 0); exit(0); } darray_free(target->u.m.items); *target = NOOBJ_OBJECT; } else { target->u.m.current_hp -= src->u.m.dmg; } } else { logstr("Target is not a monster or player"); } } static bool additem(struct monster *m, struct item *it) { darray_append(m->items, *it); return true; } static void rmvitem(struct monster *m, size_t i) { darray_remove(m->items, i); } bool inventorycallback(struct monster *m, size_t i) { struct item *it = &darray_item(m->items,i); if(it->type == USE) { m->current_hp = MIN(m->max_hp, m->current_hp+it->u.u.restorehp); rmvitem(m, i); return true; } else if(it->type == EQUIP) { if(it->u.e.isequipped) { m->max_hp -= it->u.e.hp; m->current_hp = MAX(1, m->current_hp-it->u.e.hp); m->dmg -= it->u.e.dmg; } else { m->max_hp += it->u.e.hp; m->current_hp += it->u.e.hp; m->dmg += it->u.e.dmg; } it->u.e.isequipped = !it->u.e.isequipped; } return false; } void inventory(struct monster *m) { itemselect(m, inventorycallback); } //TODO: player moves on target, create new map struct point move_object(struct game *g, struct point src, struct point target) { if(game_at(g, target)->type == NOOBJ) { *game_at(g, target) = *game_at(g, src); *game_at(g, src) = NOOBJ_OBJECT; return target; } else if(game_at(g, target)->type == ITEM && game_at(g, src)->type == MONSTER) { if(!additem(&game_at(g, src)->u.m, &game_at(g, target)->u.i)) { logstr("Inventory is full"); return src; } *game_at(g, target) = *game_at(g, src); *game_at(g, src) = NOOBJ_OBJECT; return target; } else { return src; } } static inline struct point attack_move(struct game *g, struct point src, struct point target) { struct object *t = game_at(g, target); if(t->type == MONSTER) { attack(game_at(g, src), t); } else { return move_object(g, src, target); } return src; } void run_ai(struct game *g, struct point player) { darray(unsigned) checked = darray_new(); for(size_t y = 0; y < g->ys; y++) { for(size_t x = 0; x < g->xs; x++) { pointarr res; if(game_at(g, (struct point){y,x})->type == MONSTER && !game_at(g, (struct point){y,x})->u.m.isplayer) { unsigned *id; darray_foreach(id, checked) { if(*id == game_at(g, (struct point){y,x})->u.m.id) goto next; } darray_append(checked, game_at(g, (struct point){y,x})->u.m.id); res = astar(g, (struct point){y,x}, player, AGGRO_RANGE); if(res.size >= 2) { attack_move(g, (struct point){y,x}, darray_item(res, res.size-2)); } else { struct point ps[4] = { {y, x-1}, {y, x+1}, {y-1, x}, {y+1, x} }; move_object(g, (struct point){y,x}, ps[randrange(0,3)]); } darray_free(res); } next:; } } darray_free(checked); } void loop(struct game *g) { /* "\ ███████╗██████╗ ██╗ ██╗ ███╗ ██╗███████╗ ██████╗██╗ ██╗ █████╗ ██████╗ ██████╗ ███████╗███╗ ███╗██╗ ██╗███████╗███╗ ██╗██████╗ \n\ ██╔════╝██╔══██╗██║ ██║ ████╗ ██║██╔════╝██╔════╝██║ ██╔╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝████╗ ████║██║ ██║██╔════╝████╗ ██║██╔══██╗\n\ ███████╗██████╔╝███████║ ██╔██╗ ██║█████╗ ██║ █████╔╝ ███████║██████╔╝██║ ███╗█████╗ ██╔████╔██║██║ ██║█████╗ ██╔██╗ ██║██║ ██║\n\ ╚════██║██╔══██╗██╔══██║ ██║╚██╗██║██╔══╝ ██║ ██╔═██╗ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██║\n\ ███████║██║ ██║██║ ██║ ██║ ╚████║███████╗╚██████╗██║ ██╗██║ ██║██║ ██║╚██████╔╝███████╗██║ ╚═╝ ██║╚██████╔╝███████╗██║ ╚████║██████╔╝\n\ " */ *game_at(g, (struct point){1,1}) = (struct object) { .type = MONSTER, .symbol = 'P', .u.m = { .id = -1, .name = "Player", .max_hp = 20, .current_hp = 20, .dmg = 2, .level = 1, .xp = 0, .items = darray_new(), .isplayer = true } }; struct point player = {1,1}; while(true) { charclear(); charprint("(づ。◕‿‿◕。)づ"); charprint("Name: %s", game_at(g, player)->u.m.name); charprint("HP : %u/%u", game_at(g, player)->u.m.current_hp, game_at(g, player)->u.m.max_hp); charprint("DMG: %u", game_at(g, player)->u.m.dmg); charprint("LVL: %u", game_at(g, player)->u.m.level); charprint("XP : %u", game_at(g, player)->u.m.xp); draw_map(g, player); struct choice c = get_input(); switch(c.a) { case INVALID: logstr("Invalid input, try again"); continue; case QUIT: return; case MOVE:; struct point targetpos = relative(player, c.u.d); if(!in_bounds(g, targetpos)) { logstr("Target not in bounds"); continue; } player = attack_move(g, player, targetpos); run_ai(g, player); break; case SPAWN: spawn_enemy(g); break; case INVENTORY: inventory(&game_at(g, player)->u.m); break; case REDRAW: setup(); printlog(); break; } } }