#!/usr/local/bin/cz -- # Rescue # by Sam Watkins # This code is in the public domain. # If you want to build it yourself, please contact the author; # he can try to explain the complex process! use b #int vw = 1024, vh = 600 def tile_w 32 # note, for my collision logic, size-32 tiles is good! def tiles_per_row 30 typedef uint32_t tile_bits_row typedef tile_bits_row tile_bits[tile_w] tile_bits *tiles_bits pos_xy *tiles_cog int *tiles_count #int vw = 800, vh = 480 int vw = 1024, vh = 600 int vw2, vh2 def UCHAR_TOP 256 # as large as any real map def map_max_w 256 def map_max_h 256 int level = 1 uchar map[map_max_w][map_max_h] uchar map_anim_frame[map_max_w][map_max_h] int map_w int map_h int n_tiles int n_tile_rows GLuint tiles #colour tile_colour[UCHAR_TOP] typedef gl_num pos_xy[2] struct pos: gl_num x[2] gl_num v[2] struct object: pos p object_type *type uchar anim_frame struct animal: object o int climbing_rope gl_num rope_pos int dead gl_num blood gl_num breath gl_num burning bit touched bit safe # gl_num heat # gl_num water # gl_num food struct view pos p def pos_move(p): p.x[0] += p.v[0] p.x[1] += p.v[1] def sector_size 8 def MAX_OBJS 1024 def MAX_TILE_FRAMES UCHAR_TOP def MAX_PARTICLES 1024 def ROPE_SEGMENT_LEN 2 def rope_restore_factor 20 def hero_rope_mass 15 def MAX_ROPE_POINTS 1024 def MAX_ROPES MAX_ROPE_POINTS def f_break_rope 2 int n_objs = 0 object *objs[MAX_OBJS] double cd_imp[MAX_OBJS][2] int cd_bumps[MAX_OBJS] int climbing_moves = 3 bit can_walk, can_jump, can_climb, can_swim bit underwater int climbing = 0 int digging = 0 typedef enum { OC_OBJECT=1, OC_ANIMAL } obj_class; struct object_type: uchar c object_action *act uchar t_base obj_class class object_type obj_types[] = {'I', hero_act, 0, OC_ANIMAL }, {'G', girl_act, 0, OC_ANIMAL }, {'o', gem_act, 0, OC_OBJECT }, {'$', gem_act, 0, OC_OBJECT }, {'x', gem_act, 0, OC_OBJECT }, {'R', grue_act, 0, OC_ANIMAL }, {'W', wumpus_act, 0, OC_ANIMAL }, {'O', rock_act, 0, OC_OBJECT }, {'0', rock_act, 0, OC_OBJECT }, {'i', head_act, 0, OC_OBJECT }, {'w', head_act, 0, OC_OBJECT }, {'g', head_act, 0, OC_OBJECT }, # fire, spreading? typedef void object_action(object *o) view v cstr tile_chars uchar c2tile[UCHAR_TOP] uchar tile2c[UCHAR_TOP] uchar tile_frames[MAX_TILE_FRAMES] int tile_frame_step = 8 gl_num anim_pause_prob = 0.1 #gl_num fps = 59.82 gl_num fps = 60 gl_num sleep_fps = 61 int draw_every = 1 gl_num gravity = 0.2 gl_num resist = 0.98 gl_num swim_resist = 0.95 gl_num dirt_resist = 0.95 gl_num rope_resist = 0.97 # 0.95 int collision_restore_tries = 5 gl_num hero_walk_accel = 0.12 gl_num hero_swim_accel = 0.07 gl_num hero_climb_accel = 0.10 gl_num hero_air_accel = 0.02 gl_num hero_swing_accel = 0.03 gl_num hero_dig_accel = 0.2 gl_num hero_jump = 4.8 gl_num hero_jump_boost = 1.2 gl_num init_blood = 100 gl_num blood_recover = 100.0 / 60 / 60 / 10 # 10 minutes play time to fully recover int hero_breath = 60 # bubbles! gl_num breath_recover = 3 # seconds int no_more_bubbles = 20 gl_num hold_breath = 10 # seconds gl_num d_breath gl_num hero_wumpus_injure_prob = 0.2 gl_num hero_grue_injure_prob = 0.4 gl_num hero_fire_injure_prob = 0.4 gl_num hero_thump_injure_prob = 0.5 gl_num thump_injures = 45 gl_num spike_thump_injures = 3.8 gl_num climbing_grip = 6 bit sword_out = 1 int hero_burning_hurt = 25 gl_num hero_stink = 0.2 #gl_num hero_stinks = 0.0 gl_num hero_girl_d_stink = 0.5 gl_num hero_swimming_f_stink = 0.999 gl_num hero_splash_f_stink = 0.995 gl_num hero_exertion = 0 bit draw_stink = 1 gl_num wumpus_hero_injure_prob = 1 gl_num grue_hero_injure_prob = 0.6 gl_num rope_left = 3200 struct particle: pos p short type short life gl_num v enum { PT_SMOKE, PT_SPARK, PT_SPLASH, PT_DIRT, PT_BLOOD, PT_WIND, PT_BUBBLE, PT_STINK } enum { ALIVE, DEAD_OUCH, DEAD_DROWNED, DEAD_BURNED, DEAD_FALL } enum { UT_NONE, UT_EARTH, UT_WATER, UT_AIR, UT_FIRE } int urging = UT_NONE cstr earth_chars = "*%`JLv@X" cstr gem_chars = "ox$" struct game_rope: int i0, i1 bit moving0 bit moving1 particle particles[MAX_PARTICLES] int n_particles = 0 bit paused = 0 gl_num v_zoom = 2 gl_num view_follow_factor = 1000 gl_num view_resist = 0.98 gl_num view_a = 0 gl_num view_da = 0 gl_num view_a_factor = -50 gl_num pan_force = 0.1 uchar c2objtyp[UCHAR_TOP] object *hero def hero_a (animal*)hero Uint8 *k Uint8 *k_old int n_keys = 0 bit rescue_fullscreen = 1 gl_num record_video_from = -1 int record_video_i = 0 bit record_video_enabled = 0 long frame_i = 0 gl_num game_time = 0 bit done = 0 bit restart = 0 game_rope ropes[MAX_ROPES] pos rope_points[MAX_ROPE_POINTS] int n_ropes = 0 int n_rope_points = 0 bit laying_rope = 0 enum { SFX_BURN, SFX_DIRT, SFX_DROWN, SFX_FALL, SFX_GEM, SFX_GIRL, SFX_GRUE_OUCH, SFX_GRUE, SFX_OUCH, SFX_SPLASH, SFX_DIED, SFX_WUMPUS_OUCH, SFX_WUMPUS, SFX_SAFE, SFX_TOP } # DONE SFX: SFX_BURN SFX_DIRT SFX_DROWN SFX_GEM SFX_OUCH SFX_SPLASH SFX_DIED SFX_GIRL SFX_FALL # TODO SFX: SFX_GRUE_OUCH SFX_GRUE SFX_WUMPUS_OUCH SFX_WUMPUS Mix_Chunk *sfx[SFX_TOP] bit music_playing = 1 bit sfx_enabled = 1 cstr music_path cstr music_dir cstr music_file char music_file_prev[PATH_MAX] bit fade_music_at_death = 0 load_tile_chars: tile_chars = Fslurp("tile_chars.txt") uchar t = 0, t0 for_cstr(p, tile_chars) if among(*p, '\n', '\r'): continue uchar c = *p tile2c[t] = c if c2tile[c]: t0 = c2tile[c] else: t0 = c2tile[c] = t tile_frames[t0]++ ++t n_tiles = t n_tile_rows = 1 + (n_tiles - 1) / tiles_per_row # warn("n_tiles: %d", n_tiles) int i = 1 foraryp(p, obj_types): uchar c = p->c uchar t = c2tile[c] c2objtyp[c] = i p->t_base = t ++i load_map: char map_file[80] snprintf(map_file, sizeof(map_file), "map%d.txt", level) FILE *f = Fopen(map_file) # Apparently SDL_mixer uses a symbol 'format' :S # FILE *f = Fopen(format("map%d.txt", level)) int x, y y = 0 map_w = 0 while y < map_max_h && Fgets((char*)map[y], map_max_w, f): x = 0 for_cstr(p, (char*)map[y]): if among(*p, '\r', '\n'): break int objtyp = c2objtyp[(uchar)*p] if objtyp--: object_type *t = &obj_types[objtyp] object *o animal *a which t->class: OC_OBJECT o = Talloc(object) OC_ANIMAL a = Talloc(animal) o = &a->o a->climbing_rope = -1 a->rope_pos = 0 a->blood = init_blood a->dead = 0 a->breath = hero_breath a->burning = 0 a->touched = 0 a->safe = 0 int ox = x * tile_w + tile_w/2 int oy = y * tile_w + tile_w/2 *o = (object){{ {ox,oy}, {0,0} }, &obj_types[objtyp], 0 } objs[n_objs] = o if *p == 'I': assert(!hero, "more than one hero in the map! There CAN be only ONE!") hero = o ++n_objs *p = 0 else: *p = c2tile[(uchar)*p] ++x map_w = max(map_w, x) ++y map_h = y # warn("map_w map_h: %d %d", map_w, map_h) Fclose(f) assert(hero, "no hero found in the map!") Main: Chdir(program_dir) if args: if cstr_eq(arg[0], "-w"): rescue_fullscreen = 0 shift() if args: level = atoi(arg[0]) shift() load_keys() sdl_init(vw, vh, rescue_fullscreen, program) atexit(pre_exit) rescue_gl_init() qmath_init() bits_calc_centroid_init() load_tile_chars() load_sprites() load_map() load_sfx() v.p.x[0] = hero->p.x[0] v.p.x[1] = hero->p.x[1] v.p.v[0] = 0 v.p.v[1] = 0 # char music_file[16] # snprintf(music_file, sizeof(music_file), "music/music%d", Randi(1, 4+1)) # play_music(music_file) music_path = Getenv("rescue_music", "theme.ogg") music_dir = Getenv("rescue_music_dir", "music") strncpy(music_file_prev, Getenv("rescue_music_prev", music_path), sizeof(music_file_prev)) play_random_music(music_path) gl_num i = 0 # rot_scale_fx_0() bm_enabled = 0 num start_frame = rtime() while !done: bm_start() sdl_events(done, k) # warn("done sdl_events") # glClear(GL_COLOR_BUFFER_BIT) # TODO remove if painting whole screen # if !paused: # a = Sin(i) * 3 / (1 + i/300) if frame_i % draw_every == 0: rot_scale_fx(view_a) draw_tiles(i) # warn("done draw_tiles") draw_ropes() # warn("done draw_ropes") draw_objects() # warn("done draw_objects") draw_particles() # warn("done draw_particles") draw_weapons() rot_scale_fx_done(view_a) if !paused: update() ++i # glFlush() if sleep_fps: num now = rtime() num delay = 1.0/sleep_fps - (now - start_frame) # warn("delay: %f", delay) asleep(delay, now) if frame_i % draw_every == 0: swap_buffers() start_frame = rtime() # warn("done swap_buffers") if record_video_from >= 0 && game_time >= record_video_from && record_video_enabled: record_video_frame() ++frame_i game_time = frame_i / fps bm("done render") if restart: pre_exit() sdl_pre_exit() # super dodgy restart-by-Exec! # TODO FIXME: does not preserve current fullscreen state / music etc exec_self(argv) pre_exit: if mix_open: for(i, 0, SFX_TOP): if sfx[i]: Mix_FreeChunk(sfx[i]) set_level(int level): char l[20] snprintf(l, sizeof(l), "%d", level) char *argv[] = {program_full, l, NULL} exec_self(argv) exec_self(char **argv): setenv("rescue_music_prev", music_file, 1) setenv("rescue_music", music_dir, 1) Execve(program_full, argv, environ) record_video_frame: # Systemf("xwd -name %s | xwdtopnm | pnmtopng >%08d.png", program, record_video_i++) Systemf("xwd -name %s >%08d.xwd", program, record_video_i++) draw_objects: glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) # TODO sectors glBlendFunc(GL_ONE, GL_ONE) # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) for(i, 0, n_objs): draw_object(objs[i]) glDisableClientState(GL_VERTEX_ARRAY) glDisableClientState(GL_TEXTURE_COORD_ARRAY) glDisable(GL_BLEND) glDisable(GL_TEXTURE_2D) draw_object(object *o): vpt_tile_corner(sx, o->p.x) int margin = 200 int t = obj_anim_tile(o) # simple clipping if Tween(sx[0], -vw2-margin, vw2 + margin) && Tween(sx[1], -vh2-margin, vh2 + margin): int dead = o == hero ? hero_a->dead : 0 if dead: # TODO FIXME restore colors which dead: DEAD_DROWNED glColor3f(0.3, 1, 1) DEAD_BURNED glColor3f(0.6, 0.4, 0.4) else: glColor3f(1, 1, 1) put_tile(t, sx[0], sx[1]) Def vpt(sx, wx): gl_num sx[2] = { wx[0] - v.p.x[0], wx[1] - v.p.x[1] } Def vpt_tile_corner(sx, wx): gl_num sx[2] = { wx[0] - v.p.x[0] - tile_w/2, wx[1] - v.p.x[1] - tile_w/2 } load_sprites: int tile_w_b = tile_w + 2 SDL_Surface *image tiles = load_texture("tiles.bmp", &image) assert(image->h % tile_w_b == 0 && image->w % tile_w_b == 0, "bad tiles file tiles.bmp") # n_tiles = load_texture_w / tile_w_b tiles_bits = Nalloc(tile_bits, n_tiles) tiles_cog = Nalloc(pos_xy, n_tiles) tiles_count = Nalloc(int, n_tiles) calc_tiles_bits_32bit(image) SDL_FreeSurface(image) load_sfx: int i = 0 call_each(load_sound, "burn","dirt","drown","fall","gem","girl","grue-ouch","grue","ouch","splash","died","wumpus-ouch","wumpus","safe") def load_sound(file): sfx[i++] = Mix_LoadWAV("wav/" file ".wav") # warn("loaded " file ".wav") calc_tiles_bits_24bit(SDL_Surface *s): # bm_start() int tile_w_b = tile_w + 2 uchar *pixels = s->pixels, *p int pitch = s->pitch tile_bits_row bits for(i, 0, n_tiles): int x0 = 1 + tile_w_b * i int y0 = 1 p = pixels + y0*pitch + x0*3 for(y, 0, tile_w): use(y) bits = 0 for(x, 0, tile_w): uchar r = p[2], g = p[1], b = p[0] if r + g + b > 32 * 3: bits |= 1<format->Rmask uint32_t Gmask = s->format->Gmask uint32_t Bmask = s->format->Bmask int Rshift = s->format->Rshift int Gshift = s->format->Gshift int Bshift = s->format->Bshift # uint32_t Amask = s->format->Amask int tile_w_b = tile_w + 2 uint32_t *pixels = s->pixels, *p int pitch = s->pitch / 4 tile_bits_row bits for(i, 0, n_tiles): int x0 = 1 + tile_w_b * (i % tiles_per_row) int y0 = 1 + tile_w_b * (i / tiles_per_row) p = pixels + y0*pitch + x0 for(y, 0, tile_w): use(y) bits = 0 for(x, 0, tile_w): uint r = (*p & Rmask) >> Rshift uint g = (*p & Gmask) >> Gshift uint b = (*p & Bmask) >> Bshift if r + g + b > 32 * 3: bits |= 1<>= 1 nl() nl() bit calc_tile_overlap(int tile_a, int tile_b, int dx, int dy, tile_bits_row *overlap): # if dx >= tile_w || dy >= tile_w || dx <= -tile_w || dy <= -tile_w: # zero(overlap) # return 0 tile_bits_row *a = tiles_bits[tile_a] tile_bits_row *b = tiles_bits[tile_b] bit yes = 0 # zero(overlap) int ya0 = 0, ya1 = tile_w if dy > 0 ya0 = dy for(ya, 0, ya0): overlap[ya] = 0 eif dy < 0 ya1 = tile_w+dy for(ya, ya1, tile_w): overlap[ya] = 0 if dx >= 0: _calc_tile_overlap2(<<) else: dx = -dx _calc_tile_overlap2(>>) # TODO calc centroid and width in here? no. # TODO early return at first pixel for "don't care where" case return yes def _calc_tile_overlap2(shift): for(ya, ya0, ya1): int yb = ya - dy tile_bits_row o = overlap[ya] = a[ya] & b[yb] shift dx yes = yes || o def x_to_tile(tx, x): int tx[2] = { x[0] / tile_w, x[1] / tile_w } def x_to_tile_dx(tx, dx, xy): int my(x) = xy[0], my(y) = xy[1] int tx[2] = { my(x) / tile_w, my(y) / tile_w } int dx[2] = { my(x) % tile_w, my(y) % tile_w } def obj_over_tiles(tx, ext, dx0, o): int my(x) = o->p.x[0] - tile_w/2 int my(y) = o->p.x[1] - tile_w/2 int tx[2] = { my(x) / tile_w, my(y) / tile_w } int dx0[2] = { my(x) % tile_w, my(y) % tile_w } int ext[2] = { dx0[0] ? 2 : 1, dx0[1] ? 2 : 1 } draw_tiles(gl_num i): glEnable(GL_TEXTURE_2D) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) use(i) int tx0[2], tx1[2] map_visible(tx0, tx1) glColor3f(1, 1, 1) for(x, tx0[0], tx1[0]): for(y, tx0[1], tx1[1]): int tx, ty tx = x * tile_w - v.p.x[0] ty = y * tile_w - v.p.x[1] # colour c int t = 0 # TODO move this check out of the loop? # serves to clear borders. # but glClear might be quicker / lighter on CPU # if Tween(y, 0, map_h) && Tween(x, 0, map_w): t = map_anim_tile(x, y) put_tile(t, tx, ty) # TODO use rect_fill (GL equivalent) for plotting blank space glDisableClientState(GL_VERTEX_ARRAY) glDisableClientState(GL_TEXTURE_COORD_ARRAY) glDisable(GL_TEXTURE_2D) map_visible(int tx0[2], int tx1[2]) x_to_tile(txo, v.p.x) gl_num v_rot_margin = 1.2 int tw2 = vw2/v_zoom*v_rot_margin / tile_w + 1 int th2 = vh2/v_zoom*v_rot_margin / tile_w + 1 tx0[0] = txo[0] - tw2 tx0[1] = txo[1] - th2 tx1[0] = txo[0] + tw2 tx1[1] = txo[1] + th2 if tx0[0] < 0: tx0[0] = 0 if tx0[1] < 0: tx0[1] = 0 if tx1[0] > map_w: tx1[0] = map_w if tx1[1] > map_h: tx1[1] = map_h int obj_anim_tile(object *o): # TODO do this properly based on anim sequence / state, not just dead or not! animal *a = (animal *)o if o->type->class == OC_ANIMAL && a->dead && dead_head(a): return c2tile[tolower(o->type->c)] return anim_tile(o->type->t_base, &o->anim_frame) def map_anim_tile(x, y) anim_tile(map[y][x], &map_anim_frame[y][x]) int anim_tile(uchar t_base, uchar *anim_frame): int f = *anim_frame int t = t_base + f / tile_frame_step return t obj_anim_tile_next(object *o): if o->type->class == OC_ANIMAL && ((animal *)o)->dead: return anim_tile_next(o->type->t_base, &o->anim_frame) def map_anim_tile_next(x, y) anim_tile_next(map[y][x], &map_anim_frame[y][x]) anim_tile_next(uchar t_base, uchar *anim_frame): int f = *anim_frame int fs = tile_frames[t_base] if Rand() > anim_pause_prob: *anim_frame = (f+1) % (fs * tile_frame_step) #rot_scale_fx_0: # glScalef(0.2, 0.2, 1) rot_scale_fx(gl_num a): glPushMatrix() glRotatef(a, 0, 0, 1) gl_num sf = v_zoom glScalef(sf, sf, 1) rot_scale_fx_done(gl_num a): use(a) glPopMatrix() gl_rgb(gl_num r, gl_num g, gl_num b): GLfloat c[4] = { r, g, b, 1 } glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c) gl_rand_col: GLfloat c[4] = { Rand(), Rand(), Rand(), 1 } glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c) ulong p2_ceil(ulong x): ulong p = 1 while p < x: p <<= 1 return p gl_num texture_real_w, texture_real_h GLuint load_texture(cstr file, SDL_Surface **image_): # I would not use SDL for this again! # This function would be a great reason NOT to release the source. GLuint texture GLenum texture_format int Bpp int w, h, wp2, hp2 SDL_Surface *rawimage, *rgb_bigimage, *rgba_bigimage, *bgra_bigimage, *rgba_image rawimage = SDL_LoadBMP(file) if !rawimage: failed("SDL_LoadBMP") w = rawimage->w h = rawimage->h # expand to a power of 2 size, for textures wp2 = p2_ceil(w) hp2 = p2_ceil(h) texture_real_w = (gl_num)w/wp2 texture_real_h = (gl_num)h/hp2 rgb_bigimage = SDL_CreateRGBSurface(SDL_SWSURFACE, wp2, hp2, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000) if !rgb_bigimage: failed("SDL_CreateRGBSurface") SDL_BlitSurface(rawimage, NULL, rgb_bigimage, NULL) # no proper alpha in the BMP yet - set black -> transparent SDL_SetColorKey(rgb_bigimage, SDL_SRCCOLORKEY, 0) bgra_bigimage = SDL_DisplayFormatAlpha(rgb_bigimage) if !bgra_bigimage: failed("SDL_DisplayFormatAlpha") # bogusly, fix up ABGR->RGBA or whatever is wrong with it rgba_bigimage = SDL_ConvertSurface(bgra_bigimage, rgb_bigimage->format, SDL_SWSURFACE) if !rgba_bigimage: warn("%s", SDL_GetError()) failed("SDL_ConvertSurface") # also, bogotasically, convert original sized image to RGBA for easily creating the collision bitmaps SDL_SetColorKey(rawimage, SDL_SRCCOLORKEY, 0) rgba_image = SDL_DisplayFormatAlpha(rawimage) if !rgba_image: failed("SDL_DisplayFormatAlpha") if image_: # return the original-size rgba image, for collision bitmaps etc *image_ = rgba_image else: SDL_FreeSurface(rgba_image) *image_ = NULL Bpp = rgba_bigimage->format->BytesPerPixel if Bpp != 4: error("image Bpp != 4") texture_format = rgba_bigimage->format->Rmask == 0x000000ff ? GL_RGBA : GL_BGRA glGenTextures(1, &texture) # TODO generate many textures together? glBindTexture(GL_TEXTURE_2D, texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, wp2, hp2, 0, texture_format, GL_UNSIGNED_BYTE, rgba_bigimage->pixels) SDL_FreeSurface(rgba_bigimage) SDL_FreeSurface(bgra_bigimage) SDL_FreeSurface(rawimage) SDL_FreeSurface(rgb_bigimage) return texture put_tile(int tile_n, gl_num x, gl_num y): # this uses a 'texture map' (one large texture with many tiles) int i = tile_n % tiles_per_row int j = tile_n / tiles_per_row int tile_w_b = tile_w + 2 int x1 = x + tile_w int y1 = y + tile_w GLfloat tw = texture_real_w / tiles_per_row GLfloat th = texture_real_h / n_tile_rows GLfloat px = texture_real_w / tiles_per_row / tile_w_b GLfloat py = texture_real_h / n_tile_rows / tile_w_b GLfloat tx0 = tw * i GLfloat tx1 = tx0 + tw tx0 += px tx1 -= px GLfloat ty0 = th * j GLfloat ty1 = ty0 + th ty0 += py ty1 -= py gl_tex_rect(x,y, x1,y1, tx0,ty0, tx1,ty1) update: update_objects() move_particles() move_ropes() update_view() update_anim_tiles() hero_is_underwater: d_breath = hero_breath / (hold_breath*fps) gl_num b = (hero_a->breath -= d_breath) if b >= no_more_bubbles && Rand() < 0.15: make_bubble(hero->p.x[0], hero->p.x[1] - 13, hero->p.v[0], hero->p.v[1]) eif b < 0: hero_die(DEAD_DROWNED) update_anim_tiles: for(p, objs + 0, objs + n_objs): obj_anim_tile_next(*p) int tx0[2], tx1[2] map_visible(tx0, tx1) # TODO give it a bit more border for the particle effects? for(x, tx0[0], tx1[0]): for(y, tx0[1], tx1[1]): map_anim_tile_next(x, y) if tile2c[map[y][x]] == '!' && Rand() < 0.5: int tx, ty # tx = x * tile_w - v.p.x[0] # ty = y * tile_w - v.p.x[1] tx = x * tile_w + tile_w/2 ty = y * tile_w + tile_w/2 make_smoke(tx, ty - 8 - tile_w/2, 1) make_smoke(gl_num x, gl_num y, gl_num spread): # if spread == 1: # return gl_num dx = Rand(-4, 4) gl_num dy = Rand(-5, 5) gl_num life = Rand(120, 360) new_particle(PT_SMOKE, x+dx, y+dy, dx/6*spread, dy/6/spread - 4, life, Rand(0.3, 1)) make_stink(gl_num x, gl_num y): # disabled for now return gl_num dx = Rand(-4, 4) gl_num dy = Rand(-5, 5) gl_num life = Rand(120, 360) new_particle(PT_STINK, x+dx, y+dy, dx/6, dy/6-2, life, Rand(0.3, 1)) new_particle(int type, gl_num x, gl_num y, gl_num vx, gl_num vy, int life, gl_num v): if n_particles < MAX_PARTICLES: particles[n_particles++] = (particle){ {{x,y}, {vx,vy}}, type, life, v } draw_particles: glEnableClientState(GL_VERTEX_ARRAY) # glBegin(GL_POINTS) for(i, 0, n_particles): # if (i+frame_i) % 2: # continue particle *p = &particles[i] gl_num x = p->p.x[0] - v.p.x[0] gl_num y = p->p.x[1] - v.p.x[1] gl_num r = .5 gl_num v bit tri = 1 which p->type: PT_SMOKE . v = p->v * p->life / 360.0 glColor3f(v, v, v) r += v PT_STINK . if !draw_stink: continue v = p->v * p->life / 360.0 glColor3f(v/3, v/2, v/3) r += v # glColor3f((v+pow(v, 2))/2, (v+pow(v, 3))/2, v/2) PT_SPLASH . v = p->v * p->life / 40.0 glColor3f(v, v, 1) r += v tri = 0 PT_DIRT . v = p->v glColor3f(v/2+0.5, v, (1-v)/4) r += rmod(v*10, 1) PT_BLOOD . v = p->v glColor3f(v/2+0.5, 0, (1-v)/2) r += rmod(v*10, 1) tri = 0 PT_WIND . v = p->v glColor3f(v/2+0.5, 0, (1-v)/2) r += rmod(v*10, 1) PT_BUBBLE . v = p->v glColor3f(0.9, 0.9, 0.9 + v/10) r += rmod(v*10, 1) tri = 0 if tri: gl_fill_tri(x, y-r, x-r, y+r, x+r, y+r) else: gl_fill_rect(x-r, y-r, x+r, y+r) if p->type == PT_BUBBLE: glColor3f(0, 0, 1) # gl_fill_tri(x, y-r/2, x-r/2, y+r/2, x+r/2, y+r/2) gl_fill_rect(x-r/2, y-r/2, x+r/2, y+r/2) # glVertex2d(x, y) # glEnd() glDisableClientState(GL_VERTEX_ARRAY) draw_ropes: glEnableClientState(GL_VERTEX_ARRAY) glColor3f(1, 1, 0.5) # TODO do this viewpoint transform outside around all the drawing, not here glPushMatrix() glTranslatef(-v.p.x[0], -v.p.x[1], 0) for(i, 0, n_ropes): # glBegin(GL_LINE_STRIP) int i0 = ropes[i].i0 int n = ropes[i].i1 - i0 + 1 glVertexPointer(2, GL_NUM, sizeof(pos), &rope_points[i0].x) glDrawArrays(GL_LINE_STRIP, 0, n) # for(j, ropes[i].i0, ropes[i].i1+1): # pos *p = &rope_points[j] # glVertex2d(p->x[0], p->x[1]) # if laying_rope && i == n_ropes-1: # glVertex2d(hero->p.x[0], hero->p.x[1]) # glEnd() glPopMatrix() glColor3f(1, 1, 1) glDisableClientState(GL_VERTEX_ARRAY) # TODO remove, not going to have a sword! at least not for now gl_num sword_dir = 1 gl_num sword_a = 70 gl_num sword_r0 = 8.5 gl_num sword_len = 15 gl_num sword_o[] = {0, 2.6} draw_weapons: . # # hero's sword # if hero->p.v[0] > 1: # sword_dir = 1 # eif hero->p.v[0] < -1: # sword_dir = -1 # if sword_out && 0: # TODO # gl_num r1 = sword_r0 + sword_len # gl_num s, c # glDisable(GL_TEXTURE_2D) # vpt(sx, hero->p.x) # glColor3f(0.5, 1, 1) # glBegin(GL_TRIANGLE_STRIP) # sx[0] += sword_o[0] # sx[1] += sword_o[1] # qsincos(s, c, sword_a*pi/180) # s *= sword_dir # c = -c # glVertex2d(sx[0]+sword_r0*s, sx[1]+sword_r0*c) # glVertex2d(sx[0]+r1*s, sx[1]+r1*c) # glVertex2d(sx[0]+sword_r0*s+c, sx[1]+sword_r0*c-s) # glVertex2d(sx[0]+r1*s+c/2, sx[1]+r1*c-s/2) # glEnd() # glEnable(GL_TEXTURE_2D) # glColor3f(1, 1, 1) # # if k[SDLK_RSHIFT]: # sword_a -= 3 # eif sword_a < 120: # sword_a += 6 update_objects: # TODO sectors object_actions() move_objects() collision_detect_and_restore() object_actions: for(p, objs + 0, objs + n_objs): object *o = *p o->type->act(o) move_objects: for(p, objs + 0, objs + n_objs): object *o = *p if o == hero && (climbing || digging): digging = 0 else: o->p.v[1] += gravity o->p.v[0] *= resist o->p.v[1] *= resist pos_move(o->p) int dead = o == hero ? hero_a->dead : 0 if !dead: . eif dead == DEAD_OUCH: if Rand(1) < 0.005: make_blood(o->p.x[0], o->p.x[1]+3, Rand(-1,1), Rand(-1,1)) eif dead == DEAD_BURNED: if Rand(1) < 0.1: make_smoke(o->p.x[0], o->p.x[1]-8, 1) # TODO move out? gl_num wind = 0 gl_num wind_volatility = 0.02 move_particles: wind += Rand(-wind_volatility, wind_volatility) wind = (wind + 0.03) * 0.97 - 0.03 # wind_volatility += Rand(-0.001, 0.001) # wind_volatility = (wind_volatility - 0.05) * 0.999 + 0.05 for(i, 0, n_particles): particle *p = &particles[i] pos_move(p->p) if p->type == PT_SMOKE: if particle_collide(p): p->p.x[0] -= p->p.v[0] p->p.x[1] -= p->p.v[1] p->p.v[0] = Rand(-.3, .3) p->p.v[1] = Rand(-.3, .3) else: p->p.v[0] += Rand(-.1, .1) + wind p->p.v[1] += Rand(-.1, .12) # p->life = 0 eif p->type == PT_STINK: if particle_collide(p): p->p.x[0] -= p->p.v[0] p->p.x[1] -= p->p.v[1] p->p.v[0] = Rand(-.3, .3) p->p.v[1] = Rand(-.3, .3) else: p->p.v[0] += Rand(-.3, .3) + wind p->p.v[1] += Rand(-.3, .3) # p->life = 0 eif among(p->type, PT_SPLASH, PT_DIRT): if particle_collide(p): p->life = 0 else: p->p.v[1] += gravity / 2 eif p->type == PT_BLOOD: p->v = rmod(p->v+0.001, 1) if particle_collide(p): p->p.x[0] -= p->p.v[0] * .95 p->p.x[1] -= p->p.v[1] * .95 p->p.v[0] += Rand(-.1, .1) p->p.v[1] += Rand(-.1, .11) else: p->p.v[1] += gravity eif p->type == PT_BUBBLE: p->v = rmod(p->v+0.001, 1) if particle_collide(p): p->p.x[0] -= p->p.v[0] p->p.x[1] -= p->p.v[1] p->p.v[0] = Rand(-.3,.3) p->p.v[1] = Rand(-.3,.3) p->p.v[0] *= 0.95 p->p.v[1] *= 0.95 # p->p.v[1] += gravity if --p->life <= 0: particles[i] = particles[n_particles-1] --n_particles --i move_ropes: for(i, 0, n_ropes): move_rope(i) move_rope(int rope_i): game_rope *r = &ropes[rope_i] bit moving0 = r->moving0, moving1 = r->moving1 bit laying_this_rope = laying_rope && rope_i == n_ropes-1 int i0 = r->i0 int i1 = r->i1 for(i, i0, i1+1): pos *p = &rope_points[i] if i > i0: p->v[1] += gravity p->x[0] += p->v[0] p->x[1] += p->v[1] rope_point_collide_react(p) p->v[0] *= rope_resist p->v[1] *= rope_resist gl_num x0 = rope_points[i0].x[0], y0 = rope_points[i0].x[1] gl_num x1, y1 gl_num dx, dy, d for(i, i0, i1+1): pos *p = &rope_points[i] if i < i1: x1 = p[1].x[0]; y1 = p[1].x[1] eif laying_this_rope: x1 = hero->p.x[0]; y1 = hero->p.x[1] d = hypot(dx = x1-x0, dy = y1-y0) if d > ROPE_SEGMENT_LEN: gl_num f = (d/ROPE_SEGMENT_LEN-1) / rope_restore_factor if f > f_break_rope: break_rope(rope_i, i) break if i > i0 || moving0: p[0].v[0] += f * dx ; p[0].v[1] += f * dy if moving1 ? i < i1 : i < i1-1: p[1].v[0] -= f * dx ; p[1].v[1] -= f * dy eif laying_this_rope: hero->p.v[0] -= f * dx / hero_rope_mass ; hero->p.v[1] -= f * dy / hero_rope_mass x0 = x1; y0 = y1 break_rope(int rope_i, int point): bit laying_this_rope = laying_rope && rope_i == n_ropes-1 if laying_this_rope: end_rope() else: game_rope *r0 = &ropes[rope_i] if n_ropes < MAX_ROPES && point < r0->i1: ropes[n_ropes++] = (game_rope){point+1, r0->i1, 1, r0->moving1} r0->i1 = point r0->moving1 = 1 bit particle_collide(particle *p): bit in_air = 0 # only collide with tiles for now x_to_tile_dx(tx, dx, p->p.x) if !(Tween(tx[0], 0, map_w) && Tween(tx[1], 0, map_h)): return 0 uchar what = tile2c[map[tx[1]][tx[0]]] if among(what, ' ', '.') && p->type != PT_BUBBLE: return 0 int mt = map_anim_tile(tx[0], tx[1]) bit collided = tile_has_point(mt, dx[0], dx[1]): # if collided && p->type == PT_SPLASH && what == '=': # return p->p.v[1] > 0 if collided && p->type == PT_DIRT && what == '%': return 0 if collided && p->type == PT_SMOKE && what == '!': return 0 if collided && among(p->type, PT_SPLASH, PT_BLOOD, PT_DIRT) && among(what, '=', '#'): p->p.v[0] *= 0.6 p->p.v[1] *= 0.6 return 0 if p->type == PT_BUBBLE: if among(what, '=', '#'): if collided: p->p.v[1] -= gravity p->p.v[1] *= 0.9 else: in_air = 1 collided = 0 if among(what, ' ', '.'): in_air = 1 collided = 0 if in_air: p->p.v[1] += gravity / 2 p->life /= 1.05 return collided rope_point_collide_react(pos *p): x_to_tile_dx(tx, dx, p->x) if !(Tween(tx[0], 0, map_w) && Tween(tx[1], 0, map_h)): return uchar what = tile2c[map[tx[1]][tx[0]]] if among(what, ' ', '.', '=', '#'): return int mt = map_anim_tile(tx[0], tx[1]) bit collided = tile_has_point(mt, dx[0], dx[1]): if collided: p->x[0] -= p->v[0] p->x[1] -= p->v[1] p->v[0] = 0 p->v[1] = 0 bit tile_has_point(int t, int x, int y): tile_bits_row *rows = tiles_bits[t] tile_bits_row r = rows[y] return r & 1<p.v[0] += total_imp[0] # o->p.v[1] += total_imp[1] # o->p.v[0] /= 2 # o->p.v[1] /= 2 o->p.v[0] += imp[0]/2 o->p.v[1] += imp[1]/2 o->p.v[0] *= 0.99 o->p.v[1] *= 0.99 o->p.x[0] += imp[0] o->p.x[1] += imp[1] # nl() # if o == hero: # Sayf("%d %d %d %d", tx[0], tx[1], ext[0], ext[1]) int detect_collisions(): int bumps = 0 for(obj_i, 0, n_objs): int bumps1 = detect_object_collisions(obj_i) bumps += bumps1 return bumps int detect_object_collisions(int obj_i): object *o = objs[obj_i] # TODO use sectors to avoid n^2 # TODO collision detect, bounce / damage / react tile_bits overlap # if o != hero: # return 0 # TODO remove # repeat(4): # nl() gl_num total_imp[2] = { 0, 0 } int bumps = 0 int ot = obj_anim_tile(o) object_vs_immobile_tiles() object_vs_other_objects() cd_bumps[obj_i] = bumps cd_imp[obj_i][0] = total_imp[0] cd_imp[obj_i][1] = total_imp[1] return bumps def object_vs_immobile_tiles(): # TODO factor with object_vs_other_objects obj_over_tiles(tx0, ext, dx0, o) for(i, 0, ext[0]): for(j, 0, ext[1]): int tx[2] = { tx0[0] + i, tx0[1] + j } if !(Tween(tx[0], 0, map_w) && Tween(tx[1], 0, map_h)): continue gl_num dx[2] = { dx0[0] - i*tile_w, dx0[1] - j*tile_w } uchar what = tile2c[map[tx[1]][tx[0]]] int mt = map_anim_tile(tx[0], tx[1]) # Sayf("%d %d %d %d %d %d", tx[0], tx[1], dx[0], dx[1], ot, mt) # tile_bits_row *a = tiles_bits[ot] # tile_bits_row *b = tiles_bits[mt] if calc_tile_overlap(ot, mt, -dx[0], -dx[1], overlap): # print_tile_bits(a) # print_tile_bits(b) # print_tile_bits(overlap) gl_num imp[2], cent[2] int count = calc_overlap_impulse(imp, cent, tiles_cog[ot], overlap) int bumps1 = collision_react(obj_i, &map[tx[1]][tx[0]], -1, what, dx, cent, imp, count, overlap) bumps += bumps1 total_imp[0] += imp[0] total_imp[1] += imp[1] # Sayf("imp: %f %f", imp[0], imp[1]) def object_vs_other_objects(): # TODO factor with object_vs_immobile_tiles for(o1_i, 0, n_objs): object *o1 = objs[o1_i] if o1 == o || !o: # I don't know why o can be NULL, when you jump off top of the screen! but this avoids it! continue gl_num dx[2] = { o1->p.x[0] - o->p.x[0], o1->p.x[1] - o->p.x[1] } if Abs(dx[0]) < tile_w && Abs(dx[1]) < tile_w: int what = o1->type->c int o1t = obj_anim_tile(o1) if calc_tile_overlap(ot, o1t, dx[0], dx[1], overlap): # print_tile_bits(a) # print_tile_bits(b) # print_tile_bits(overlap) gl_num imp[2], cent[2] int count = calc_overlap_impulse(imp, cent, tiles_cog[ot], overlap) # we give half the impulse to the other object, for now imp[0] /= 2 ; imp[1] /= 2 int bumps1 = collision_react(obj_i, NULL, o1_i, what, dx, cent, imp, count, overlap) bumps += bumps1 total_imp[0] += imp[0] total_imp[1] += imp[1] # Sayf("imp: %f %f", imp[0], imp[1]) int collision_react(int obj_i, uchar *tile, int o1_i, uchar c, gl_num dx[2], gl_num cent[2], gl_num imp[2], int count, tile_bits overlap): use(overlap) object *o = objs[obj_i] object *o1 = o1_i >= 0 ? objs[o1_i] : 0 animal *a = (animal*)o animal *a1 = (animal*)o1 uchar t = o->type->c # stars - no effect if c == '.': imp[0] = imp[1] = 0 return 0 # digging - cannot be moved! if o == hero && digging: if hypot(imp[0], imp[1]) < 0.6: imp[0] = imp[1] = 0 # water - buoyant if among(c, '#', '='): imp[0] = 0 imp[1] = count * -0.0002 # gl_num resist = pow(swim_resist, count / 300) # o->p.v[0] *= resist # o->p.v[1] *= resist if o == hero && count >= 20: can_swim = 1 if (c == '=' && dx[1] >= 15) || (c == '#' && dx[1] >= 0): underwater = 1 # warn("dy: %f", dx[1]) # warn("might splash: %f", cent[1]) # print_tile_bits(overlap) if c == '=' && cent[1] >= -8: # TODO this is a bit bogus, will make double powered # splashes when you land between two tiles! but it looks okay anyway num v = hypot(o->p.v[0], o->p.v[1]) if v > 1.5 && Rand()/v < 0.05: if o == hero: hero_stink *= hero_splash_f_stink make_splash(o->p.x[0] + cent[0], o->p.x[1] + cent[1], hypot(o->p.v[0], o->p.v[1])) return count # check for ground - can jump / walk if o == hero && cent[1] >= 10: can_jump = 1 can_walk = 1 # if o == hero && Abs(cent[0]) >= 5: if o == hero && cent[1] >= -15: can_climb = 1 if climbing: imp[0] /= climbing_grip imp[1] /= climbing_grip # o->p.x[0] += Abs(cent[0]) / cent[0] * 2 # dirt - diggable if o == hero && c == '%': digging = 1 if Abs(dx[0]) < 12 && Abs(dx[1]) < 12: *tile = c2tile[' '] repeat(50): make_dirt(o->p.x[0] + cent[0], o->p.x[1] + cent[1], dx[0]/2+Rand(-5,5), dx[1]/2+Rand(-5,5)) play_sfx(SFX_DIRT) o->p.v[0] *= dirt_resist o->p.v[1] *= dirt_resist imp[0] = imp[1] = 0 return 0 gl_num vx = o->p.v[0], vy = o->p.v[1] gl_num v = hypot(vx, vy) gl_num impv = hypot(imp[0], imp[1]) # wumpus and grue and fire - harmful! if o == hero && among(c, 'W', 'R', '!'): if cent[1] >= 12 && among(c, 'W', 'R'): if hero->p.v[1] > 1: monster_injured(a1, cent, count) eif !a1 || !a1->dead: hero_injured(c, cent, count) if c == '!': imp[0] /= 10 imp[1] /= 10 return count # girl if o == hero && c == 'G' && !a1->touched: hero_touch_girl(a1) # girl safe on the flowers if t == 'G' && c == '+' && !a->safe: a->safe = 1 play_sfx(SFX_SAFE) # thumping into anything too hard can cause injury # TODO do this for animals also gl_num thump = count*impv*v int this_thump_injures = among(c, 'v', '^') ? spike_thump_injures : thump_injures if o == hero && thump >= this_thump_injures: hero_thumped(thump / this_thump_injures, cent) # dusty earth - throws up dirt, can pin rope if strchr(earth_chars, c) || c == '^': if c != '^' && v > 2: if Rand() < 0.2: make_dirt(o->p.x[0] + cent[0], o->p.x[1] + cent[1]-2, -vx/4, -vy/4-0.2) if o == hero && !laying_rope && k[k_rope]: start_rope(o->p.x[0] + cent[0], o->p.x[1] + cent[1]) if o == hero && laying_rope && k[k_fix]: fix_rope(o->p.x[0] + cent[0], o->p.x[1] + cent[1]) return count # gems - collect them if o == hero && o1 && !a->dead && strchr(gem_chars, o1->type->c): hero_get(o1_i) play_sfx(SFX_GEM) # normal - default impulse okay return count hero_thumped(gl_num thump, gl_num cent[2]) object *o = hero int ouch = 0 repeat(thump): if Rand() < hero_thump_injure_prob: gl_num a = Rand(0, 2*pi), r = Rand(10), s, c qsincos(s, c, a) ++ouch make_blood(o->p.x[0] + cent[0] + r*s, o->p.x[1] + cent[1] + r*c, o->p.v[0], o->p.v[1]) hero_ouch(ouch, 0) int hero_injured(uchar c, gl_num cent[2], int count): # TODO generalize so other animals can be injured too? object *o = hero gl_num hero_injure_prob = c == 'W' ? hero_wumpus_injure_prob : c == 'R' ? hero_grue_injure_prob : hero_fire_injure_prob gl_num vx = o->p.v[0], vy = o->p.v[1] int ouch = 0 repeat(1+count/10): if Rand() < hero_injure_prob: gl_num x = cent[0]*.8 gl_num y = cent[1]*.8 x += Rand(-3, 3) y += Rand(-5, 5) y = clamp(y, -16, 16) if c == '!': if ++hero_a->burning >= hero_burning_hurt: ++ouch # fprintf(stderr, "burning: %f ", hero_a->burning) make_smoke(o->p.x[0] + x, o->p.x[1] + y, 1) play_sfx(SFX_BURN) else: # fprintf(stderr, "not yet burning: %f ", hero_a->burning) else: ++ouch make_blood(o->p.x[0] + x, o->p.x[1] + y, vx + Rand(-3, 3), vy + Rand(-6, 3)) if ouch: hero_ouch(ouch, c == '!') return count # TODO join with hero_injured? int monster_injured(animal *a, gl_num cent[2], int count): count *= 2 # its too hard! object *o = &a->o uchar c = o->type->c play_sfx(c == 'W' ? SFX_WUMPUS_OUCH : SFX_GRUE_OUCH) # warn("monster_injured %f", cent[1]) gl_num monster_injure_prob = c == 'W' ? wumpus_hero_injure_prob : grue_hero_injure_prob gl_num vx = o->p.v[0], vy = o->p.v[1] int ouch = 0 repeat(1+count/10): if Rand() < monster_injure_prob: gl_num x = cent[0]*.8 gl_num y = cent[1]*.8 x += Rand(-3, 3) y += Rand(-5, 5) y = clamp(y, -16, 16) ++ouch make_blood(hero->p.x[0] + x, hero->p.x[1] + y, vx + Rand(-3, 3), vy + Rand(-6, 3)) if ouch: monster_ouch(a, ouch) return count hero_ouch(int ouch, int burn) play_sfx(SFX_OUCH) hero_a->blood -= ouch # fprintf(stderr, "ouch: %d ", hero_a->blood) if hero_a->blood <= 0 && !hero_a->dead: if burn: hero_die(DEAD_BURNED) else: hero_die(DEAD_OUCH) # TODO join with hero_ouch? monster_ouch(animal *a, int ouch) a->blood -= ouch # fprintf(stderr, "monster ouch: %d ", a->blood) if a->blood <= 0 && !a->dead: animal_die(a, DEAD_OUCH) hero_die(int death_type): if hero_a->dead: return animal_die(hero_a, death_type) int sfx_i = -1 which death_type: DEAD_DROWNED sfx_i = SFX_DROWN DEAD_FALL sfx_i = SFX_FALL else sfx_i = SFX_DIED if sfx_i >= 0: play_sfx(sfx_i) if music_playing && fade_music_at_death: Mix_FadeOutMusic(3000) sword_out = 0 climbing = 0 if laying_rope: end_rope() animal_die(animal *a, int death_type): object *o = &a->o if a->dead: return a->dead = death_type if dead_head(a) && o->type->c != 'R': o->p.x[1] -= 14 # head offset! o->p.v[0] += Rand(-4, 4) o->p.v[1] += Rand(-4, 0) def dead_head(a) among(a->dead, DEAD_OUCH, DEAD_BURNED) start_rope(gl_num x, gl_num y): if rope_left <= 0 || n_rope_points >= MAX_ROPE_POINTS || n_ropes >= MAX_ROPES: return laying_rope = 1 game_rope *r = &ropes[n_ropes] ++n_ropes r->i0 = r->i1 = n_rope_points r->moving0 = 0 r->moving1 = 1 rope_points[n_rope_points] = (pos){{x,y}, {0,0}} ++n_rope_points rope_left -= ROPE_SEGMENT_LEN # warn("start_rope") continue_rope(gl_num x, gl_num y, gl_num vx, gl_num vy): if rope_left > 0 && n_rope_points < MAX_ROPE_POINTS: game_rope *r = &ropes[n_ropes-1] pos *p = &rope_points[r->i1] gl_num d = hypot(x - p->x[0], y - p->x[1]) if d >= ROPE_SEGMENT_LEN: gl_num x0 = p->x[0] + (x - p->x[0]) / d * ROPE_SEGMENT_LEN gl_num y0 = p->x[1] + (y - p->x[1]) / d * ROPE_SEGMENT_LEN r->i1 = n_rope_points rope_points[n_rope_points] = (pos){{x0,y0},{vx,vy}} ++n_rope_points rope_left -= ROPE_SEGMENT_LEN # warn("continue_rope") if rope_left <= 0 || n_rope_points >= MAX_ROPE_POINTS: end_rope() shorten_rope: game_rope *r = &ropes[n_ropes-1] if r->i1 > r->i0 + 1: gl_num dx = rope_points[r->i1-1].x[0] - hero->p.x[0] gl_num dy = rope_points[r->i1-1].x[1] - hero->p.x[1] hero->p.v[0] += dx / 20 hero->p.v[1] += dy / 20 hero->p.x[0] += hero->p.v[0] hero->p.x[1] += hero->p.v[1] -- r->i1 --n_rope_points rope_left += ROPE_SEGMENT_LEN # warn("shorten_rope") fix_rope(gl_num x, gl_num y): if rope_left > 0 && n_rope_points < MAX_ROPE_POINTS: game_rope *r = &ropes[n_ropes-1] ++r->i1 pos *p = &rope_points[r->i1] p->x[0] = x, p->x[1] = y r->moving1 = 0 ++n_rope_points rope_left -= ROPE_SEGMENT_LEN laying_rope = 0 # warn("fix_rope") end_rope: laying_rope = 0 # warn("end_rope") make_splash(gl_num x, gl_num y, gl_num v): # TODO use angle / sin / cos not rand x y, for circular/ovular emission? gl_num life = Rand(30, 60) gl_num px = Rand(v*.4, v*.7) * Randi(2)*2-1 gl_num py = -1-Rand(0, v*1.5) new_particle(PT_SPLASH, x+px, y+py, px, py, life, Rand(0.5, 1)) # TODO fix, at the moment, it can play many of these splash sfx # at once. Too loady, not ideal? play_sfx(SFX_SPLASH) make_dirt(gl_num x, gl_num y, gl_num vx, gl_num vy): gl_num v = hypot(vx, vy) gl_num life = Rand(30, 60) gl_num px = vx / v * Rand(4, 6) gl_num py = vy / v * Rand(4, 6) new_particle(PT_DIRT, x+px + Rand(-2,2), y+py + Rand(-2, 2), px, py, life, Rand()) make_blood(gl_num x, gl_num y, gl_num vx, gl_num vy): if hero_a->blood > -500: gl_num life = Rand(600, 1200) new_particle(PT_BLOOD, x, y, vx, vy, life, Rand()) # warn("blood: %d / %d", hero_a->blood, init_blood) make_wind(gl_num x, gl_num y, gl_num vx, gl_num vy): gl_num life = Rand(400, 500) new_particle(PT_WIND, x, y, vx, vy, life, Rand()) make_bubble(gl_num x, gl_num y, gl_num vx, gl_num vy): gl_num life = Rand(100, 200) new_particle(PT_BUBBLE, x, y, vx + Rand(-0.5, 0.5), vy + Rand(-1, 0), life, Rand()) hero_get(int o1_i): objs[o1_i] = objs[n_objs-1] --n_objs int calc_overlap_impulse(gl_num imp[2], gl_num cent[2], gl_num o_cog[2], tile_bits ov): int count = bits_calc_centroid(cent, ov) cent[0] -= o_cog[0] cent[1] -= o_cog[1] # Sayf("cent: %f %f", cent[0], cent[1]) # impulse is in opposite direction # gl_num w = bits_calc_width(cent, ov) # if w < 1: # w = 1 gl_num w = count / 30.0 gl_num centv_len = hypot(cent[0], cent[1]) imp[0] = -cent[0] / centv_len * w imp[1] = -cent[1] / centv_len * w return count int calc_overlap_impulse_sillier(gl_num imp[2], tile_bits ov): gl_num cent[2] int count = bits_calc_centroid(cent, ov) # Sayf("cent: %f %f", cent[0], cent[1]) # impulse is in opposite direction if cent[0] >= 0: imp[0] = -1 if cent[0] < 0: imp[0] = 1 if cent[1] >= 0: imp[1] = -1 if cent[1] < 0: imp[1] = 1 return count uchar byte_bits_count[UCHAR_TOP] uchar byte_bits_total_pos[UCHAR_TOP] bits_calc_centroid_init: for(i, 0, UCHAR_TOP): int count = 0 int total_pos = 0 int s = i while s: ++count int lowbit = s^(s&(s-1)) int lowbit_n = round(log(lowbit)/log(2)) total_pos += lowbit_n s = s & (s-1) byte_bits_count[i] = count byte_bits_total_pos[i] = total_pos # Sayd(byte_bits_count[11]) # Sayd(byte_bits_total_pos[44]) # Sayn(uint32_calc_centroid(-1)) def uint32_calc_centroid_params(count, total_pos, i): _uint32_calc_centroid(count, total_pos, i, my(b0), my(b1), my(b2), my(b3), my(t0), my(t1), my(t2), my(t3), my(c0), my(c1), my(c2), my(c3)): def _uint32_calc_centroid(count, total_pos, i, b0, b1, b2, b3, t0, t1, t2, t3, c0, c1, c2, c3): int b0, b1, b2, b3, t0, t1, t2, t3, c0, c1, c2, c3 b0 = i & 0xff b1 = (i>>8) & 0xff b2 = (i>>16) & 0xff b3 = (i>>24) & 0xff t0 = byte_bits_total_pos[b0] c0 = byte_bits_count[b0] t1 = byte_bits_total_pos[b1] c1 = byte_bits_count[b1] t2 = byte_bits_total_pos[b2] c2 = byte_bits_count[b2] t3 = byte_bits_total_pos[b3] c3 = byte_bits_count[b3] count = c0 + c1 + c2 + c3 total_pos = t0 + t1+(c1*8) + t2+(c2*16) + t3+(c3*24) gl_num uint32_calc_centroid(uint32_t i): int count, total_pos uint32_calc_centroid_params(count, total_pos, i) return (gl_num)total_pos / count int bits_calc_centroid(gl_num cent[2], tile_bits ov): int x_total_pos = 0, y_total_pos = 0, count = 0 for(i, 0, tile_w): int row_count, row_x_total_pos tile_bits_row r = ov[i] uint32_calc_centroid_params(row_count, row_x_total_pos, r) count += row_count x_total_pos += row_x_total_pos y_total_pos += i * row_count cent[0] = (gl_num)x_total_pos / count - tile_w/2 cent[1] = (gl_num)y_total_pos / count - tile_w/2 return count gl_num bits_calc_width(gl_num vec[2], tile_bits ov): use(vec, ov) # TODO disenfake it return 1 update_view: gl_num old_x = v.p.x[0] pos_move(v.p) bounce(v.p.x[0], v.p.v[0], vw2/v_zoom, <, 0.8) bounce(v.p.x[0], v.p.v[0], map_w * tile_w - vw2/v_zoom, >, 0.8) bounce(v.p.x[1], v.p.v[1], vh2/v_zoom, <, 0.8) bounce(v.p.x[1], v.p.v[1], map_h * tile_w - vh2/v_zoom, >, 0.8) gl_num real_dx = v.p.x[0] - old_x gl_num d[2] = { hero->p.x[0] - v.p.x[0], hero->p.x[1] - v.p.x[1] } v.p.v[0] += d[0] / view_follow_factor v.p.v[1] += d[1] / view_follow_factor view_a += real_dx / view_a_factor v.p.v[0] *= view_resist v.p.v[1] *= view_resist view_a *= view_resist if k[k_pan_r]: v.p.v[0] += pan_force if k[k_pan_l]: v.p.v[0] -= pan_force if k[k_pan_u]: v.p.v[1] -= pan_force if k[k_pan_d]: v.p.v[1] += pan_force gl_num fz = 1.01 if k[k_in]: v_zoom *= fz if k[k_out]: v_zoom /= fz rescue_gl_init: vw2 = vw / 2 vh2 = vh / 2 glEnable(GL_TEXTURE_2D) glClearColor(0, 0, 0, 0) glViewport(0, 0, vw, vh) glClear(GL_COLOR_BUFFER_BIT) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrthof(-vw2, vw2, vh2, -vh2, -1, 1) glMatrixMode(GL_MODELVIEW) glLoadIdentity() # glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT) glDisable(GL_DEPTH_TEST) glDisable(GL_LIGHTING) # glEnableClientState(GL_VERTEX_ARRAY) ## glEnableClientState(GL_COLOR_ARRAY) # glEnableClientState(GL_TEXTURE_COORD_ARRAY) def sdl_events(done, k): int n_keys_ SDL_Event event while SDL_PollEvent(&event): which event.type: SDL_QUIT done = 1 k = SDL_GetKeyState(&n_keys_) if n_keys_ > n_keys: Renalloc(k_old, Uint8, n_keys_) zero(k_old+n_keys, k_old+n_keys_) n_keys = n_keys_ if k[k_exit] || k[k_quit]: done = 1 restart = k[k_quit] if typed(k_flscr) && !must_be_fullscreen: rescue_fullscreen ^= 1 SDL_WM_ToggleFullScreen(sdl_surface) if typed(k_pause): paused ^= 1 if typed(k_bench): bm_enabled ^= 1 if typed(k_vrec): if record_video_from < 0: record_video_from = game_time else: record_video_from = -1 if typed(k_music): if music_playing: Mix_PauseMusic() else: Mix_ResumeMusic() music_playing ^= 1 if typed(k_musnxt): stop_music() music_path = music_dir play_random_music(music_path) eif typed(k_musprv): stop_music() if !*music_file_prev: strncpy(music_file_prev, music_file, sizeof(music_file_prev)) else: music_file = music_file_prev play_music(music_file_prev) if typed(k_sndfx): sfx_enabled ^= 1 play_sfx(SFX_GEM) # hack for 4 levels (only!) if typed(SDLK_1): set_level(1) if typed(SDLK_2): set_level(2) if typed(SDLK_3): set_level(3) if typed(SDLK_4): set_level(4) if typed(SDLK_5): set_level(5) if typed(SDLK_6): set_level(6) if typed(SDLK_7): set_level(7) if typed(SDLK_8): set_level(8) if typed(SDLK_9): set_level(9) def typed(sym) key_delta(k, k_old, sym) > 0 int key_delta(Uint8 *k, Uint8 *old, int sym): int delta = k[sym] - old[sym] old[sym] = k[sym] return delta # This did not work: #fs_hide_cursor: # SDL_ShowCursor(fullscreen ? SDL_DISABLE : SDL_ENABLE) # mover funcs hero_act(object *o): hero_a->burning *= 0.95 hero_exertion *= 0.95 # warn("%f", hero_a->burning) # warn("%d %d %d", (int)(map_w*tile_w+tile_w/4), (int)o->p.x[0], (int)o->p.x[1]) if !(Tween(o->p.x[0], -tile_w/4, map_w*tile_w + tile_w/4) && o->p.x[1] < map_h*tile_w) && !laying_rope: hero_die(DEAD_FALL) if underwater: hero_is_underwater() else: if hero_a->breath < hero_breath: hero_a->breath += hero_breath / (breath_recover*fps) underwater = 0 if climbing: --climbing if digging && hypot(hero->p.v[0], hero->p.v[1]) < 0.1: hero->p.v[0] = hero->p.v[1] = 0 # if hero_stink >= hero_stinks && Rand() < hero_stink: if Rand() < hero_stink: make_stink(hero->p.x[0], hero->p.x[1]) if can_swim: hero_stink *= hero_swimming_f_stink # warn("stink: %f", hero_stink) if hero_a->dead: return if hero_a->blood < init_blood: hero_a->blood += blood_recover # warn("%f", hero_a->blood) gl_num dv = digging ? hero_dig_accel : can_walk ? hero_walk_accel : can_swim ? hero_swim_accel : can_climb ? hero_climb_accel : laying_rope ? hero_swing_accel : hero_air_accel # o->p.v[1] -= 0.01 if k[k_left]: o->p.v[0] -= dv if can_climb && climbing: climbing = climbing_moves if k[k_right]: o->p.v[0] += dv if can_climb && climbing: climbing = climbing_moves if k[k_down]: o->p.v[1] += dv if can_climb: climbing = climbing_moves if k[k_up]: o->p.v[1] -= dv if can_climb: climbing = climbing_moves if typed(k_jump) && can_jump: if k[k_left] o->p.v[0] -= hero_jump_boost if k[k_right] o->p.v[0] += hero_jump_boost o->p.v[1] /= 2 o->p.v[1] -= hero_jump if laying_rope: if k[k_rope]: continue_rope(o->p.x[0], o->p.x[1], o->p.v[0], o->p.v[1]) if typed(k_roll): shorten_rope() if typed(k_cut): end_rope() urging = UT_NONE if k[k_urge]: urging = urge() int urge_power = 50 int urge(): int type = UT_NONE # based on point_collide() # only test tiles for now x_to_tile_dx(tx, dx, v.p.x) if !(Tween(tx[0], 0, map_w) && Tween(tx[1], 0, map_h)): return type uchar what = tile2c[map[tx[1]][tx[0]]] int mt = map_anim_tile(tx[0], tx[1]) bit collided = tile_has_point(mt, dx[0], dx[1]): gl_num x = v.p.x[0], y = v.p.x[1] gl_num vx = Rand(-15, 15), vy = Rand(-15, 15) x += vx ; y += vy if !collided || among(what, ' ', '.'): type = UT_AIR # make_wind(x, y, vx, vy) eif strchr(earth_chars, what): repeat(urge_power): make_dirt(x, y, vx, vy) type = UT_EARTH eif among(what, '#', '='): # make_splash(x, y, vx, vy) repeat(urge_power): make_splash(x, y, Abs(vx)/3) type = UT_WATER eif what == '!': repeat(urge_power): make_smoke(x, y, 3) type = UT_FIRE # if type != UT_NONE: # warn("urging %s: particles: %d", urge_type_s[type], n_particles) return type cstr urge_type_s[] = { "", "EARTH", "WATER", "AIR", "FIRE" } gem_act(object *o): use(o) rock_act(object *o): use(o) # TODO FIXME monsters can sometimes 'double jump'! cool, tho gl_num wumpus_chase_d = 150 gl_num wumpus_accel = 0.09 gl_num wumpus_jump_prob = 0.016 gl_num wumpus_jump = 3 gl_num grue_chase_d = 120 gl_num grue_accel = 0.11 gl_num grue_jump_prob = 0.008 gl_num grue_jump = 3.5 gl_num girl_follow_d = 250 gl_num girl_accel = 0.06 gl_num girl_jump_prob = 0.002 gl_num girl_jump = 2 gl_num monster_sfx_d = 50 wumpus_act(object *o): # TODO use stink # TODO 'chasing' state # TODO climb / jump animal *a = (animal *)o if a->dead: return if a->blood < init_blood: a->blood += blood_recover gl_num dx = hero->p.x[0] - o->p.x[0] gl_num dy = hero->p.x[1] - o->p.x[1] gl_num d = hypot(dx, dy) if d < wumpus_chase_d: gl_num jump = 0 if d < monster_sfx_d && !a->touched: a->touched = 1 play_sfx(SFX_WUMPUS) jump = 0.5 o->p.v[0] += dx/d * Rand(0.9, 1.1) * wumpus_accel o->p.v[1] += dy/d * Rand(0.9, 1.1) * wumpus_accel if jump || Rand() < wumpus_jump_prob: o->p.v[1] -= wumpus_jump * (jump+1) grue_act(object *o): # TODO use stink # TODO 'chasing' state # TODO climb / jump animal *a = (animal *)o if a->dead: return if a->blood < init_blood: a->blood += blood_recover gl_num dx = hero->p.x[0] - o->p.x[0] gl_num dy = hero->p.x[1] - o->p.x[1] gl_num d = hypot(dx, dy) if d < grue_chase_d: gl_num jump = 0 if d < monster_sfx_d && !a->touched: a->touched = 1 play_sfx(SFX_GRUE) jump = 0.5 o->p.v[0] += dx/d * grue_accel o->p.v[1] += dy/d * grue_accel if jump || Rand() < grue_jump_prob: o->p.v[1] -= grue_jump * (jump+1) girl_act(object *o): # TODO use stink # TODO 'chasing' state # TODO climb / jump animal *a = (animal *)o if a->dead: return # girls can't be injured yet, but anyway... if a->blood < init_blood: a->blood += blood_recover if !a->touched || a->safe: return gl_num dx = hero->p.x[0] - o->p.x[0] gl_num dy = hero->p.x[1] - o->p.x[1] gl_num d = hypot(dx, dy) if d < girl_follow_d: o->p.v[0] += dx/d * girl_accel o->p.v[1] += dy/d * girl_accel if Rand() < girl_jump_prob: o->p.v[1] -= girl_jump head_act(object *o): use(o) play_sfx(int sfx_i): if sfx_enabled: # TODO stop excessive repeated sound effects? # warn("play_sfx: %d", sfx_i) Mix_PlayChannel(-1, sfx[sfx_i], 0) # Mix_PlayChannel(sfx_i, sfx[sfx_i], 0) char rand_music_path[PATH_MAX] play_random_music(cstr music_path): if music_file: strncpy(music_file_prev, music_file, sizeof(music_file_prev)) if is_dir(music_path): vec *files = Ls(music_path) int i = Randi(veclen(files)) cstr file = *(cstr*)v(files, i) snprintf(rand_music_path, PATH_MAX, "music/%s", file) music_file = rand_music_path else: music_file = music_path play_music(music_file) hero_touch_girl(animal *girl): girl->touched = 1 hero_stink += 0.5 play_sfx(SFX_GIRL) def keys_file "keys.txt" SDLKey k_left, k_right, k_up, k_down, k_jump, k_rope, k_roll, k_cut, k_fix, k_urge, k_out, k_in, k_pan_l, k_pan_r, k_pan_u, k_pan_d, k_exit, k_quit, k_flscr, k_pause, k_bench, k_vrec, k_music, k_sndfx, k_musnxt, k_musprv struct action_key: cstr name SDLKey *key_code_ptr action_key action_keys_table[] = { "left", &k_left }, { "right", &k_right }, { "up", &k_up }, { "down", &k_down }, { "jump", &k_jump }, { "rope", &k_rope }, { "roll", &k_roll }, { "cut", &k_cut }, { "fix", &k_fix }, { "urge", &k_urge }, { "out", &k_out }, { "in", &k_in }, { "pan_l", &k_pan_l }, { "pan_r", &k_pan_r }, { "pan_u", &k_pan_u }, { "pan_d", &k_pan_d, }, { "exit", &k_exit }, { "quit", &k_quit }, { "flscr", &k_flscr }, { "pause", &k_pause }, { "bench", &k_bench }, { "vrec", &k_vrec }, { "music", &k_music }, { "sndfx", &k_sndfx }, { "musnxt", &k_musnxt }, { "musprv", &k_musprv } hashtable struct__action_keys_by_name hashtable *action_keys_by_name = &struct__action_keys_by_name action_keys_init: hashtable_init(action_keys_by_name) action_key *p action_key *action_keys_table_end = array_end(action_keys_table) for p = action_keys_table; p!=action_keys_table_end; ++p put(action_keys_by_name, p->name, p) SDLKey *action_key_code_ptr_by_name(cstr name) action_key *k = get(action_keys_by_name, name) return k ? k->key_code_ptr : NULL load_keys: sdl_keys_init() action_keys_init() if !exists(keys_file): Symlink("keys-"^^device_keyboard^^".txt", keys_file) F_in(keys_file): Eachline(l): trim0(l) if !*l || *l == '#': continue cstr action = l cstr key_name = l + strcspn(l, " \t") if !*key_name: error("missing key for action: %s", l) *key_name++ = '\0' trim(key_name) #TODO trim SDLKey key_code = sdl_key_code_by_name(key_name) # warn("key def: %s = %s = %d", action, key_name, key_code) SDLKey *key_code_ptr = action_key_code_ptr_by_name(action) if !key_code_ptr: error("unknown key action: %s for key: %s", action, key_name) *key_code_ptr = key_code # Exit() def trim(s): s = trim_(s) cstr trim_(cstr s): trim0(s) trim1(s) return s def trim0(s): s = trim0_(s) cstr trim0_(cstr s): while isspace((uchar)*s): ++s return s trim1(cstr s): char *e = s+strlen(s) while e != s && isspace((uchar)e[-1]): *--e = '\0'