forked from mirrors/principia
1519 lines
42 KiB
C++
1519 lines
42 KiB
C++
#include "pkgman.hh"
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <dirent.h>
|
|
|
|
#include "progress.hh"
|
|
#include "zlib.h"
|
|
#include "misc.hh"
|
|
|
|
#ifdef TMS_BACKEND_WINDOWS
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
// for lvledit command line tool
|
|
#ifdef _NO_TMS
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <cinttypes>
|
|
|
|
#define tms_infof(...)
|
|
#define tms_warnf(...)
|
|
#define tms_fatalf(...)
|
|
#define tms_errorf(...)
|
|
|
|
static const char *tms_storage_path() { return "/tmp/"; }
|
|
static const char *tms_storage_cache_path() { return "/tmp/"; }
|
|
#else
|
|
#include <tms/bindings/cpp/cpp.hh>
|
|
#endif
|
|
|
|
static char _level_path[4][1024];
|
|
static char _state_path[1024];
|
|
static char _cache_path[4][1024];
|
|
static char _cache_state_path[1024];
|
|
static char _pkg_path[4][1024];
|
|
static const char *_dir_names[] = {
|
|
"local", "db", "main", "sys"
|
|
};
|
|
|
|
lvlbuf tmpbuf;
|
|
lvlinfo tmplvl;
|
|
|
|
void lvlinfo::create(int type, uint64_t seed, uint32_t version) {
|
|
this->local_id = 0;
|
|
this->save_id = 0;
|
|
this->version = (version ? version : LEVEL_VERSION);
|
|
this->type = type;
|
|
this->name[0] = '\0';
|
|
this->name_len = 0;
|
|
|
|
if (this->type == LCAT_PARTIAL) {
|
|
return;
|
|
}
|
|
|
|
this->community_id = 0;
|
|
this->autosave_id = 0;
|
|
this->revision = 0;
|
|
this->parent_id = 0;
|
|
this->parent_revision = 0;
|
|
this->pause_on_finish = true;
|
|
this->show_score = (type == LCAT_ADVENTURE);
|
|
this->descr_len = 0;
|
|
this->visibility = LEVEL_VISIBLE;
|
|
|
|
static const int random_bgs[] = {
|
|
0,1,2,4,8,9,10,11,12,13,14
|
|
};
|
|
static const int random_bgs_adventure[] = {
|
|
0,1,2,4,5,8,9,10,11,12,13,14
|
|
};
|
|
|
|
if (type == LCAT_ADVENTURE) {
|
|
this->bg = random_bgs_adventure[rand()%(sizeof(random_bgs_adventure)/sizeof(int))];
|
|
} else {
|
|
this->bg = random_bgs[rand()%(sizeof(random_bgs)/sizeof(int))];
|
|
}
|
|
|
|
this->bg_color = 0;
|
|
|
|
this->descr = 0;
|
|
this->size_x[0] = 40;
|
|
this->size_x[1] = 40;
|
|
this->size_y[0] = 4;
|
|
this->size_y[1] = 10;
|
|
this->gravity_x = 0;
|
|
this->gravity_y = -20.f;
|
|
this->flags = 0;
|
|
this->min_x = 0;
|
|
this->max_x = 0;
|
|
this->min_y = 0;
|
|
this->max_y = 0;
|
|
this->final_score = 0;
|
|
memset(this->icon, 0, 128*128);
|
|
|
|
this->sandbox_cam_x = 0.f;
|
|
this->sandbox_cam_y = 0.f;
|
|
this->sandbox_cam_zoom = 12.f;
|
|
|
|
this->velocity_iterations = 10;
|
|
this->position_iterations = 10;
|
|
|
|
this->seed = 0;
|
|
this->adventure_id = 0xffffffff;
|
|
|
|
this->prismatic_tolerance = 0.0125f;
|
|
this->pivot_tolerance = 0.0125f;
|
|
|
|
this->linear_damping = 0.1f;
|
|
this->angular_damping = 0.2f;
|
|
this->joint_friction = 0.3f;
|
|
if (type == LCAT_ADVENTURE) {
|
|
this->dead_enemy_absorb_time = 60.0f;
|
|
} else {
|
|
this->dead_enemy_absorb_time = 10.0f;
|
|
}
|
|
this->time_before_player_can_respawn = 1.5f;
|
|
this->compression_length = 0;
|
|
|
|
this->flags |= LVL_ALLOW_RESPAWN_WITHOUT_CHECKPOINT;
|
|
|
|
if (type == LCAT_ADVENTURE) {
|
|
this->flags |= LVL_ABSORB_DEAD_ENEMIES;
|
|
this->flags |= LVL_ENABLE_INTERACTIVE_DESTRUCTION;
|
|
this->flags |= LVL_DEAD_CREATURE_DESTRUCTION;
|
|
|
|
if (seed) {
|
|
this->flags |= LVL_CHUNKED_LEVEL_LOADING;
|
|
this->flags |= LVL_ALLOW_QUICKSAVING;
|
|
this->show_score = false;
|
|
this->seed = seed;
|
|
this->size_y[0] = 500;
|
|
this->bg = 5;
|
|
}
|
|
}
|
|
|
|
this->num_groups = 0;
|
|
this->num_entities = 0;
|
|
this->num_connections = 0;
|
|
this->num_cables = 0;
|
|
this->num_chunks = 0;
|
|
this->state_size = 0;
|
|
this->num_gentypes = 0;
|
|
}
|
|
|
|
void lvlinfo::sanity_check() {
|
|
if (this->version < LEVEL_VERSION_1_5) {
|
|
// remove flags added in 1.5
|
|
this->flags &= ~(
|
|
LVL_CHUNKED_LEVEL_LOADING |
|
|
LVL_DISABLE_CAVEVIEW |
|
|
LVL_DISABLE_ROCKET_TRIGGER_EXPLOSIVES |
|
|
LVL_STORE_SCORE_ON_GAME_OVER |
|
|
LVL_ALLOW_HIGH_SCORE_SUBMISSIONS |
|
|
LVL_LOWER_SCORE_IS_BETTER |
|
|
LVL_DISABLE_ENDSCREENS |
|
|
LVL_ALLOW_QUICKSAVING |
|
|
LVL_DEAD_CREATURE_DESTRUCTION
|
|
);
|
|
|
|
// add flags added in 1.5 that must be enabled by default
|
|
this->flags |= (
|
|
LVL_ALLOW_RESPAWN_WITHOUT_CHECKPOINT
|
|
);
|
|
|
|
this->dead_enemy_absorb_time = 10.0f;
|
|
this->time_before_player_can_respawn = 1.5f;
|
|
this->linear_damping = 0.0f;
|
|
this->angular_damping = 0.0f;
|
|
this->joint_friction = 0.0f;
|
|
this->seed = 0;
|
|
this->adventure_id = 0xffffffff;
|
|
this->compression_length = 0;
|
|
}
|
|
}
|
|
|
|
int lvlinfo::get_size() const {
|
|
return sizeof(uint8_t) // version
|
|
+ sizeof(uint8_t) // type
|
|
+ sizeof(uint32_t) // community_id
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0) // autosave_id
|
|
+ sizeof(uint32_t) // revision
|
|
+ sizeof(uint32_t) // parent_id
|
|
+ sizeof(uint8_t) // name_len
|
|
+ sizeof(uint16_t) // descr_len
|
|
+ sizeof(uint8_t) // allow_derivatives
|
|
+ (this->version >= 3 ? sizeof(uint8_t) : 0) // visibility
|
|
+ (this->version >= 7 ? sizeof(uint32_t) : 0) // parent_revision
|
|
+ (this->version >= 7 ? sizeof(uint8_t) : 0) // pause_on_finish
|
|
+ (this->version >= 7 ? sizeof(uint8_t) : 0) // show_score
|
|
|
|
+ sizeof(uint8_t) // bg
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0) // bg color, stored as uint32_t
|
|
|
|
+ sizeof(uint16_t) // size_x
|
|
+ sizeof(uint16_t) // size_y
|
|
+ (this->version>=12 ? sizeof(uint16_t) : 0) // size_x 2
|
|
+ (this->version>=12 ? sizeof(uint16_t) : 0) // size_y 2
|
|
|
|
+ sizeof(uint8_t) // velocity_iterations
|
|
+ sizeof(uint8_t) // position_iterations
|
|
+ sizeof(uint32_t) // final_score
|
|
+ sizeof(float) // sandbox_cam_x
|
|
+ sizeof(float) // sandbox_cam_y
|
|
+ sizeof(float) // sandbox_cam_zoom
|
|
+ (this->version >= 3 ? sizeof(float) : 0) // gravity_x
|
|
+ (this->version >= 3 ? sizeof(float) : 0) // gravity_y
|
|
+ (this->version >= 13 ? sizeof(float)*4 : 0) // bounds
|
|
+ (this->version >= 9 ? sizeof(uint64_t) : 0) // flags
|
|
|
|
+ (this->version >= LEVEL_VERSION_1_4 ? sizeof(float) : 0) // prismatic_tolerance
|
|
+ (this->version >= LEVEL_VERSION_1_4 ? sizeof(float) : 0) // pivot_tolerance
|
|
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint64_t) : 0) // seed
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0) // adventure_id
|
|
|
|
// num groups, entities, connections, cables, chunks, state_size, num_gentypes
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? 7 * sizeof(uint32_t) : 4 * sizeof(uint16_t))
|
|
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // linear damping
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // angular damping
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // joint friction
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // dead enemy absorb time
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(float) : 0) // time before player can respawn
|
|
+ (this->version >= LEVEL_VERSION_1_5 ? sizeof(uint64_t) : 0) // compression buffer length
|
|
|
|
+ this->name_len
|
|
+ (this->version >= 6 ? 128*128 : 0)
|
|
+ this->descr_len;
|
|
}
|
|
|
|
void lvlinfo::write(lvlbuf *lb) {
|
|
this->sanity_check();
|
|
|
|
lb->ensure(this->get_size());
|
|
|
|
lb->w_uint8(this->version);
|
|
lb->w_uint8(this->type);
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
lb->w_uint32(this->community_id);
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
lb->w_uint32(this->autosave_id);
|
|
}
|
|
|
|
lb->w_uint32(this->revision);
|
|
lb->w_uint32(this->parent_id);
|
|
}
|
|
|
|
lb->w_uint8(this->name_len);
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
lb->w_uint16(this->descr_len);
|
|
lb->w_uint8(1);
|
|
if (this->version >= 3) {
|
|
lb->w_uint8(this->visibility);
|
|
}
|
|
if (this->version >= 7) {
|
|
lb->w_uint32(this->parent_revision);
|
|
lb->w_uint8(this->pause_on_finish);
|
|
lb->w_uint8(this->show_score);
|
|
}
|
|
lb->w_uint8(this->bg);
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
lb->w_uint32(this->bg_color);
|
|
}
|
|
|
|
if (this->version >= 12) {
|
|
lb->w_uint16(this->size_x[0]);
|
|
lb->w_uint16(this->size_x[1]);
|
|
lb->w_uint16(this->size_y[0]);
|
|
lb->w_uint16(this->size_y[1]);
|
|
} else {
|
|
lb->w_uint16(this->size_x[0]+this->size_x[1]);
|
|
lb->w_uint16(this->size_y[0]+this->size_y[1]);
|
|
}
|
|
|
|
lb->w_uint8(this->velocity_iterations);
|
|
lb->w_uint8(this->position_iterations);
|
|
lb->w_uint32(this->final_score);
|
|
lb->w_float(this->sandbox_cam_x);
|
|
lb->w_float(this->sandbox_cam_y);
|
|
lb->w_float(this->sandbox_cam_zoom);
|
|
|
|
if (this->version >= 3) {
|
|
lb->w_float(this->gravity_x);
|
|
lb->w_float(this->gravity_y);
|
|
}
|
|
}
|
|
|
|
if (this->version >= 13) {
|
|
lb->w_float(this->min_x);
|
|
lb->w_float(this->max_x);
|
|
lb->w_float(this->min_y);
|
|
lb->w_float(this->max_y);
|
|
}
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
if (this->version >= 9) {
|
|
lb->w_uint64(this->flags);
|
|
}
|
|
|
|
if (this->version >= LEVEL_VERSION_1_4) {
|
|
lb->w_float(this->prismatic_tolerance);
|
|
lb->w_float(this->pivot_tolerance);
|
|
}
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
lb->w_uint64(this->seed);
|
|
lb->w_uint32(this->adventure_id);
|
|
lb->w_float(this->linear_damping);
|
|
lb->w_float(this->angular_damping);
|
|
lb->w_float(this->joint_friction);
|
|
lb->w_float(this->dead_enemy_absorb_time);
|
|
lb->w_float(this->time_before_player_can_respawn);
|
|
lb->w_uint64(this->compression_length);
|
|
}
|
|
}
|
|
|
|
if (this->name_len) {
|
|
lb->w_buf(this->name, this->name_len);
|
|
}
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
if (this->version >= 6) {
|
|
int nn = 0;
|
|
int n_max = 0;
|
|
for (int y=0; y<128*128; y++) {
|
|
if (icon[y] != 0) nn ++;
|
|
if (icon[y] > n_max) n_max = icon[y];
|
|
}
|
|
lb->w_buf((const char *)&this->icon, 128*128);
|
|
}
|
|
if (this->descr_len)
|
|
lb->w_buf(this->descr, this->descr_len);
|
|
}
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
lb->w_uint32(this->num_groups);
|
|
lb->w_uint32(this->num_entities);
|
|
lb->w_uint32(this->num_connections);
|
|
lb->w_uint32(this->num_cables);
|
|
lb->w_uint32(this->num_chunks);
|
|
lb->w_uint32(this->state_size);
|
|
lb->w_uint32(this->num_gentypes);
|
|
} else {
|
|
lb->w_uint16((uint16_t)this->num_groups);
|
|
lb->w_uint16((uint16_t)this->num_entities);
|
|
lb->w_uint16((uint16_t)this->num_connections);
|
|
lb->w_uint16((uint16_t)this->num_cables);
|
|
}
|
|
}
|
|
|
|
bool lvlinfo::read(lvlbuf *lb, bool skip_description) {
|
|
this->version = lb->r_uint8();
|
|
if (this->version > LEVEL_VERSION) {
|
|
return false;
|
|
}
|
|
|
|
this->type = lb->r_uint8();
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
this->community_id = lb->r_uint32();
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
this->autosave_id = lb->r_uint32();
|
|
}
|
|
|
|
this->revision = lb->r_uint32();
|
|
this->parent_id = lb->r_uint32();
|
|
}
|
|
|
|
this->name_len = lb->r_uint8();
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
this->descr_len = lb->r_uint16();
|
|
// XXX: Get rid of this value in next level version (allow_derivatives)
|
|
(void)lb->r_uint8();
|
|
if (this->version >= 3)
|
|
this->visibility = lb->r_uint8();
|
|
else
|
|
this->visibility = LEVEL_VISIBLE;
|
|
if (this->version >= 7) {
|
|
this->parent_revision = lb->r_uint32();
|
|
this->pause_on_finish = (bool)lb->r_uint8();
|
|
this->show_score = (bool)lb->r_uint8();
|
|
} else {
|
|
this->parent_revision = 0;
|
|
this->pause_on_finish = true;
|
|
this->show_score = 1;
|
|
}
|
|
this->bg = lb->r_uint8();
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
this->bg_color = lb->r_uint32();
|
|
}
|
|
|
|
if (this->version >= 12) {
|
|
this->size_x[0] = lb->r_uint16();
|
|
this->size_x[1] = lb->r_uint16();
|
|
this->size_y[0] = lb->r_uint16();
|
|
this->size_y[1] = lb->r_uint16();
|
|
} else {
|
|
this->size_x[0] = lb->r_uint16();
|
|
this->size_y[0] = lb->r_uint16();
|
|
this->size_x[1] = this->size_x[0];
|
|
this->size_y[1] = this->size_y[0];
|
|
}
|
|
|
|
this->velocity_iterations = lb->r_uint8();
|
|
this->position_iterations = lb->r_uint8();
|
|
this->final_score = lb->r_uint32();
|
|
this->sandbox_cam_x = lb->r_float();
|
|
this->sandbox_cam_y = lb->r_float();
|
|
this->sandbox_cam_zoom = lb->r_float();
|
|
|
|
if (this->version >= 3) {
|
|
this->gravity_x = lb->r_float();
|
|
this->gravity_y = lb->r_float();
|
|
} else {
|
|
this->gravity_x = 0.f;
|
|
this->gravity_y = -20.f;
|
|
}
|
|
}
|
|
|
|
if (this->version >= 13) {
|
|
min_x = lb->r_float();
|
|
max_x = lb->r_float();
|
|
min_y = lb->r_float();
|
|
max_y = lb->r_float();
|
|
} else {
|
|
min_x = 0.f;
|
|
max_x = 0.f;
|
|
min_y = 0.f;
|
|
max_y = 0.f;
|
|
}
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
if (this->version >= 9) {
|
|
this->flags = lb->r_uint64();
|
|
|
|
if (this->version < LEVEL_VERSION_1_5) {
|
|
this->flags |= LVL_ALLOW_RESPAWN_WITHOUT_CHECKPOINT;
|
|
}
|
|
} else {
|
|
this->flags = 0;
|
|
}
|
|
|
|
if (this->version >= 26) {
|
|
this->prismatic_tolerance = lb->r_float();
|
|
this->pivot_tolerance = lb->r_float();
|
|
} else {
|
|
this->prismatic_tolerance = 0.f;
|
|
this->pivot_tolerance = 0.f;
|
|
}
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
this->seed = lb->r_uint64();
|
|
this->adventure_id = lb->r_uint32();
|
|
this->linear_damping = lb->r_float();
|
|
this->angular_damping = lb->r_float();
|
|
this->joint_friction = lb->r_float();
|
|
this->dead_enemy_absorb_time = lb->r_float();
|
|
this->time_before_player_can_respawn = lb->r_float();
|
|
this->compression_length = lb->r_uint64();
|
|
} else {
|
|
this->seed = 0;
|
|
this->adventure_id = 0xffffffff;
|
|
this->linear_damping = 0.f;
|
|
this->angular_damping = 0.f;
|
|
this->joint_friction = 0.f;
|
|
this->dead_enemy_absorb_time = 10.f;
|
|
this->time_before_player_can_respawn = 0.f;
|
|
this->compression_length = 0;
|
|
}
|
|
}
|
|
|
|
if (this->name_len) {
|
|
lb->r_buf(this->name, this->name_len);
|
|
}
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
if (this->version >= 6) {
|
|
//tms_infof(" reading icon, pt: %d, end: %d", lb->rp, lb->size);
|
|
lb->r_buf((char *)&this->icon, 128*128);
|
|
} else
|
|
memset(this->icon, 0, 128*128);
|
|
|
|
if (this->descr_len && !skip_description) {
|
|
this->descr = (char*)realloc(this->descr, this->descr_len+1);
|
|
lb->r_buf(this->descr, this->descr_len);
|
|
this->descr[this->descr_len] = '\0';
|
|
}
|
|
}
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
this->num_groups = lb->r_uint32();
|
|
this->num_entities = lb->r_uint32();
|
|
this->num_connections = lb->r_uint32();
|
|
this->num_cables = lb->r_uint32();
|
|
this->num_chunks = lb->r_uint32();
|
|
this->state_size = lb->r_uint32();
|
|
this->num_gentypes = lb->r_uint32();
|
|
} else {
|
|
this->num_groups = lb->r_uint16();
|
|
this->num_entities = lb->r_uint16();
|
|
this->num_connections = lb->r_uint16();
|
|
this->num_cables = lb->r_uint16();
|
|
this->num_chunks = 0;
|
|
this->state_size = 0;
|
|
this->num_gentypes = 0;
|
|
}
|
|
|
|
this->sanity_check();
|
|
|
|
return true;
|
|
}
|
|
|
|
void lvlinfo::print() const {
|
|
printf("Level headers:\n");
|
|
|
|
printf("Level version: %u\t(%s)\n",
|
|
this->version, level_version_string(this->version));
|
|
|
|
printf("Level type: %u\t(%s)\n",
|
|
this->type, level_type_string(this->type));
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
printf("Community ID: %u\n",
|
|
this->community_id);
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
printf("Autosave ID: %u\n",
|
|
this->autosave_id);
|
|
}
|
|
|
|
printf("Revision: %u\n",
|
|
this->revision);
|
|
|
|
printf("Parent ID: %u\n",
|
|
this->parent_id);
|
|
}
|
|
|
|
printf("Name len: %u\n",
|
|
this->name_len);
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
printf("Description len: %u\n",
|
|
this->descr_len);
|
|
|
|
printf("Visibility: %s\n",
|
|
level_visibility_string(this->visibility));
|
|
|
|
printf("Visibility: %u\n",
|
|
this->parent_revision);
|
|
|
|
printf("Pause on finish: %s\n",
|
|
this->pause_on_finish ? "yes" : "no");
|
|
|
|
printf("Show score: %s\n",
|
|
this->show_score ? "yes" : "no");
|
|
|
|
printf("Background ID: %u\n",
|
|
this->bg);
|
|
|
|
if (this->version >= LEVEL_VERSION_1_5) {
|
|
printf("Background color: %u\n",
|
|
this->bg_color);
|
|
}
|
|
|
|
printf("Border left: %u\n",
|
|
this->size_x[0]);
|
|
|
|
printf("Border right: %u\n",
|
|
this->size_x[1]);
|
|
|
|
printf("Border down: %u\n",
|
|
this->size_y[0]);
|
|
|
|
printf("Border up: %u\n",
|
|
this->size_y[1]);
|
|
|
|
printf("Vel iterations: %u\n",
|
|
this->velocity_iterations);
|
|
|
|
printf("Pos iterations: %u\n",
|
|
this->position_iterations);
|
|
|
|
printf("Final score: %u\n",
|
|
this->final_score);
|
|
|
|
printf("Sandbox cam X: %f\n",
|
|
this->sandbox_cam_x);
|
|
|
|
printf("Sandbox cam Y: %f\n",
|
|
this->sandbox_cam_y);
|
|
|
|
printf("Sandbox cam Z: %f\n",
|
|
this->sandbox_cam_zoom);
|
|
|
|
if (this->version >= 3) {
|
|
printf("Gravity X: %.6f\n",
|
|
this->gravity_x);
|
|
printf("Gravity Y: %.6f\n",
|
|
this->gravity_y);
|
|
}
|
|
}
|
|
|
|
printf("Min/Max X: %.2f/%.2f\n",
|
|
this->min_x,
|
|
this->max_x);
|
|
|
|
printf("Min/Max Y: %.2f/%.2f\n",
|
|
this->min_y,
|
|
this->max_y);
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
printf("Flags: %" PRIu64 "\n",
|
|
this->flags);
|
|
|
|
printf("Prismatic tolerance: %.2f\n",
|
|
this->prismatic_tolerance);
|
|
|
|
printf("Pivot tolerance: %.2f\n",
|
|
this->pivot_tolerance);
|
|
|
|
printf("Seed: 0x%016" PRIx64 "\n",
|
|
this->seed);
|
|
|
|
printf("Adventure ID: 0x%08" PRIx32 "\n",
|
|
this->adventure_id);
|
|
|
|
printf("Linear damping: %.2f\n",
|
|
this->linear_damping);
|
|
|
|
printf("Angular damping: %.2f\n",
|
|
this->angular_damping);
|
|
|
|
printf("Joint friction: %.2f\n",
|
|
this->joint_friction);
|
|
|
|
printf("Enemy absorb time: %.2f\n",
|
|
this->dead_enemy_absorb_time);
|
|
|
|
printf("Player respawn time: %.2f\n",
|
|
this->time_before_player_can_respawn);
|
|
}
|
|
|
|
if (this->name_len) {
|
|
printf("Name: (%u) '%s'\n",
|
|
(unsigned)strlen(this->name), this->name);
|
|
}
|
|
|
|
if (this->type != LCAT_PARTIAL) {
|
|
if (this->descr_len) {
|
|
printf("Description: (%u) '%s'\n",
|
|
(unsigned)strlen(this->descr), this->descr);
|
|
}
|
|
}
|
|
|
|
printf("Num groups: %u\n",
|
|
this->num_groups);
|
|
|
|
printf("Num entities: %u\n",
|
|
this->num_entities);
|
|
|
|
printf("Num connections: %u\n",
|
|
this->num_connections);
|
|
|
|
printf("Num chunks: %u\n",
|
|
this->num_chunks);
|
|
|
|
printf("Num gentypes: %u\n",
|
|
this->num_gentypes);
|
|
|
|
printf("State size: %u\n",
|
|
this->state_size);
|
|
|
|
printf("Num cables: %u\n",
|
|
this->num_cables);
|
|
}
|
|
|
|
// pkginfo
|
|
|
|
uint32_t pkginfo::get_next_level(uint32_t id) {
|
|
if (this->return_on_finish) {
|
|
if (this->first_is_menu)
|
|
return this->levels[0];
|
|
else
|
|
return 0; // return to pkg screen
|
|
}
|
|
|
|
// find the level in the package, return the next level or 0 if it was the last level
|
|
for (int x=0; x<this->num_levels-1; x++) {
|
|
if (this->levels[x] == id)
|
|
return this->levels[x+1];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool pkginfo::is_level_locked(uint8_t index) {
|
|
uint8_t i_unlocked = 0;
|
|
int x = 0;
|
|
|
|
if (index >= this->num_levels) return true;
|
|
if (this->unlock_count == 0) return false;
|
|
|
|
if (this->first_is_menu) {
|
|
x = 1;
|
|
i_unlocked++;
|
|
}
|
|
|
|
for (; x<index; ++x) {
|
|
lvl_progress *p = progress::get_level_progress(this->type, this->levels[x]);
|
|
if (p->completed)
|
|
i_unlocked++;
|
|
}
|
|
|
|
if ((index - i_unlocked) < this->unlock_count)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pkginfo::add_level(uint32_t id) {
|
|
// make sure it isnt already added
|
|
for (int x=0; x<this->num_levels; x++)
|
|
if (this->levels[x] == id) return false;
|
|
|
|
this->levels = (uint32_t*)realloc(this->levels, sizeof(uint32_t)*(this->num_levels+1));
|
|
|
|
if (!this->levels) exit(1);
|
|
|
|
this->levels[this->num_levels] = id;
|
|
this->num_levels ++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pkginfo::open(int type, uint32_t id) {
|
|
if (type < 0 || type >= 4) {
|
|
tms_errorf("invalid level type");
|
|
return false;
|
|
}
|
|
|
|
char path[1024];
|
|
char *storage = (char*)pkgman::get_pkg_path(type);
|
|
snprintf(path, 1023, "%s/%d.ppkg", storage, id);
|
|
|
|
this->type = type;
|
|
this->id = id;
|
|
|
|
if (this->levels) free(this->levels);
|
|
this->levels = 0;
|
|
this->num_levels = 0;
|
|
this->name[0] = '\0';
|
|
this->name[255] = '\0';
|
|
|
|
FILE_IN_ASSET(type == LEVEL_MAIN);
|
|
|
|
FILE *fp = _fopen(path, "rb");
|
|
|
|
if (!fp) {
|
|
tms_errorf("could not open: %s", path);
|
|
return false;
|
|
}
|
|
|
|
_fread(&this->version, 1, 1, fp);
|
|
if (this->version >= 2)
|
|
_fread(&this->community_id, 1, sizeof(uint32_t), fp);
|
|
else
|
|
this->community_id = 0;
|
|
|
|
_fread(this->name, 1, 255, fp);
|
|
|
|
if (this->version >= 3)
|
|
_fread(&this->unlock_count, 1, sizeof(uint8_t), fp);
|
|
|
|
if (this->version >= 1) {
|
|
_fread(&this->first_is_menu, 1, sizeof(uint8_t), fp);
|
|
_fread(&this->return_on_finish, 1, sizeof(uint8_t), fp);
|
|
}
|
|
|
|
_fread(&this->num_levels, 1, sizeof(uint8_t), fp);
|
|
|
|
if (this->num_levels)
|
|
this->levels = (uint32_t*)malloc(this->num_levels * sizeof(uint32_t));
|
|
|
|
for (int x=0; x<this->num_levels; x++) {
|
|
_fread(&this->levels[x], 1, sizeof(uint32_t), fp);
|
|
}
|
|
|
|
_fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pkginfo::save() {
|
|
if (this->type >= 3) {
|
|
tms_errorf("invalid level type");
|
|
return false;
|
|
}
|
|
|
|
char path[1024];
|
|
char *storage = (char*)pkgman::get_pkg_path(this->type);
|
|
snprintf(path, 1023, "%s/%d.ppkg", storage, this->id);
|
|
|
|
return save_to_path(path);
|
|
}
|
|
|
|
bool pkginfo::save_to_path(const char *path) {
|
|
FILE *fp = fopen(path, "wb");
|
|
|
|
if (!fp) {
|
|
tms_errorf("could not open: %s", path);
|
|
return false;
|
|
}
|
|
|
|
// always update the version to the latest on save
|
|
this->version = PKG_VERSION;
|
|
|
|
fwrite(&this->version, 1, 1, fp);
|
|
fwrite(&this->community_id, 1, sizeof(uint32_t), fp);
|
|
fwrite(this->name, 1, 255, fp);
|
|
fwrite(&this->unlock_count, 1, sizeof(uint8_t), fp);
|
|
fwrite(&this->first_is_menu, 1, sizeof(uint8_t), fp);
|
|
fwrite(&this->return_on_finish, 1, sizeof(uint8_t), fp);
|
|
fwrite(&this->num_levels, 1, sizeof(uint8_t), fp);
|
|
|
|
for (int x=0; x<this->num_levels; x++) {
|
|
fwrite(&this->levels[x], 1, sizeof(uint32_t), fp);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
// pkgman
|
|
|
|
static uint32_t get_next_id(const char *storage, const char *ext) {
|
|
char path[1024];
|
|
struct stat s;
|
|
|
|
// Check IDs up to 2 billion. Ought to be enough for everyone.
|
|
for (uint32_t x = 1; x < 2e9; x++) {
|
|
snprintf(path, 1023, "%s/%d.%s", storage, x, ext);
|
|
int i = stat(path, &s);
|
|
|
|
if (i == -1)
|
|
return x;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t pkgman::get_next_pkg_id() {
|
|
return get_next_id(pkgman::get_pkg_path(LEVEL_LOCAL), "ppkg");
|
|
}
|
|
|
|
uint32_t pkgman::get_next_level_id() {
|
|
return get_next_id(pkgman::get_level_path(LEVEL_LOCAL), "plvl");
|
|
}
|
|
|
|
uint32_t pkgman::get_next_object_id() {
|
|
return get_next_id(pkgman::get_level_path(LEVEL_LOCAL), "pobj");
|
|
}
|
|
|
|
const char *pkgman::get_pkg_path(int type) {
|
|
if (type < 0 || type >= 4) {
|
|
tms_errorf("invalid level type");
|
|
return "";
|
|
}
|
|
|
|
if (!_pkg_path[type][0]) {
|
|
if (type == LEVEL_MAIN)
|
|
// main levels are stored internally in data/
|
|
snprintf(_pkg_path[type], 1023, "data/pkg");
|
|
else if (type == LEVEL_DB)
|
|
snprintf(_pkg_path[type], 1023, "%s/pkg/db", tms_storage_cache_path());
|
|
else
|
|
snprintf(_pkg_path[type], 1023, "%s/packages", tms_storage_path());
|
|
}
|
|
|
|
return _pkg_path[type];
|
|
}
|
|
|
|
const char *pkgman::get_level_ext(int level_type) {
|
|
if (level_type == LEVEL_PARTIAL) return "pobj";
|
|
else if (level_type >= LEVEL_LOCAL_STATE) return "psav";
|
|
else return "plvl";
|
|
}
|
|
|
|
const char *pkgman::get_level_path(int level_type) {
|
|
if (level_type == LEVEL_PARTIAL) level_type = LEVEL_LOCAL;
|
|
|
|
if (level_type >= LEVEL_LOCAL_STATE) {
|
|
if (!_state_path[0])
|
|
snprintf(_state_path, 1023, "%s/saves", tms_storage_path());
|
|
|
|
return _state_path;
|
|
}
|
|
|
|
if (level_type < 0 || level_type >= 4) {
|
|
tms_errorf("invalid level type");
|
|
return "";
|
|
}
|
|
|
|
if (!_level_path[level_type][0]) {
|
|
if (level_type == LEVEL_MAIN)
|
|
// main levels are stored internally in data/
|
|
snprintf(_level_path[level_type], 1023, "data/lvl");
|
|
else if (level_type == LEVEL_SYS)
|
|
snprintf(_level_path[level_type], 1023, "%s/cache/local", tms_storage_cache_path());
|
|
else if (level_type == LEVEL_DB)
|
|
snprintf(_level_path[level_type], 1023, "%s/lvl/db", tms_storage_cache_path());
|
|
else
|
|
snprintf(_level_path[level_type], 1023, "%s/levels", tms_storage_path());
|
|
}
|
|
|
|
return _level_path[level_type];
|
|
}
|
|
|
|
const char *pkgman::get_state_prefix(int level_type) {
|
|
if (level_type >= LEVEL_LOCAL_STATE)
|
|
level_type -= LEVEL_LOCAL_STATE;
|
|
|
|
if (level_type == LEVEL_LOCAL)
|
|
return "local";
|
|
else if (level_type == LEVEL_DB)
|
|
return "db";
|
|
|
|
tms_errorf("Unknown state prefix for level type %d", level_type);
|
|
return "unknown";
|
|
}
|
|
|
|
const char *pkgman::get_cache_path(int level_type) {
|
|
if (level_type == LEVEL_PARTIAL) level_type = LEVEL_LOCAL;
|
|
|
|
if (level_type >= LEVEL_LOCAL_STATE) {
|
|
if (!_cache_state_path[0]) {
|
|
snprintf(_cache_state_path, 1023,
|
|
"%s/cache/sav",
|
|
tms_storage_cache_path());
|
|
}
|
|
|
|
return _cache_state_path;
|
|
}
|
|
|
|
if (level_type < 0 || level_type >= 4) {
|
|
tms_errorf("invalid level type");
|
|
return "";
|
|
}
|
|
|
|
if (!_cache_path[level_type][0]) {
|
|
snprintf(_cache_path[level_type], 1023,
|
|
"%s/cache/%s",
|
|
tms_storage_cache_path(),
|
|
_dir_names[level_type]);
|
|
}
|
|
|
|
return _cache_path[level_type];
|
|
}
|
|
|
|
void pkgman::get_level_full_path(int level_type, uint32_t id, uint32_t save_id, char *output) {
|
|
if (level_type >= LEVEL_LOCAL_STATE) {
|
|
snprintf(output, 1023, "%s/%s.%d.%u.%s",
|
|
pkgman::get_level_path(level_type),
|
|
pkgman::get_state_prefix(level_type),
|
|
id,
|
|
save_id,
|
|
pkgman::get_level_ext(level_type));
|
|
} else {
|
|
snprintf(output, 1023, "%s/%d.%s",
|
|
pkgman::get_level_path(level_type),
|
|
id,
|
|
pkgman::get_level_ext(level_type));
|
|
}
|
|
}
|
|
|
|
void pkgman::get_cache_full_path(int level_type, uint32_t id, uint32_t save_id, char *output) {
|
|
if (level_type >= LEVEL_LOCAL_STATE) {
|
|
snprintf(output, 1023, "%s/%s.%u.%u.pcache",
|
|
pkgman::get_cache_path(level_type),
|
|
pkgman::get_state_prefix(level_type),
|
|
id,
|
|
save_id);
|
|
} else {
|
|
snprintf(output, 1023, "%s/%d.pcache",
|
|
pkgman::get_cache_path(level_type),
|
|
id);
|
|
}
|
|
}
|
|
|
|
bool pkgman::get_level_name(int level_type, uint32_t id, uint32_t save_id, char *output) {
|
|
uint8_t o_version;
|
|
pkgman::get_level_data(level_type, id, save_id, output, &o_version);
|
|
return true;
|
|
}
|
|
|
|
bool pkgman::get_level_data(int level_type, uint32_t id, uint32_t save_id, char *o_name, uint8_t *o_version) {
|
|
char filename[1024];
|
|
|
|
pkgman::get_level_full_path(level_type, id, save_id, filename);
|
|
|
|
FILE *fp = fopen(filename, "rb");
|
|
|
|
if (!fp) {
|
|
tms_warnf("unable to open file for lid %u", id);
|
|
return false;
|
|
}
|
|
|
|
tmpbuf.reset();
|
|
tmpbuf.ensure(sizeof(lvlinfo));
|
|
|
|
fread(tmpbuf.buf, 1, sizeof(lvlinfo), fp);
|
|
tmpbuf.size = sizeof(lvlinfo);
|
|
|
|
tmplvl.read(&tmpbuf, true);
|
|
|
|
if (tmplvl.name_len == 0) {
|
|
strcpy(o_name, "<no name>");
|
|
} else {
|
|
memcpy(o_name, tmplvl.name, tmplvl.name_len*sizeof(char));
|
|
o_name[tmplvl.name_len] = '\0';
|
|
}
|
|
|
|
*o_version = tmplvl.version;
|
|
|
|
fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
pkginfo *pkgman::get_pkgs(int type) {
|
|
if (type < 0 || type >= 4)
|
|
return 0;
|
|
|
|
const char *path = pkgman::get_pkg_path(type);
|
|
|
|
DIR *dir;
|
|
struct dirent *ent;
|
|
pkginfo *first = 0, *curr = 0;
|
|
|
|
if ((dir = opendir(path))) {
|
|
|
|
while ((ent = readdir(dir))) {
|
|
int len = strlen(ent->d_name);
|
|
if (len > 5 && memcmp(&ent->d_name[len-5], ".ppkg", 5) == 0) {
|
|
uint32_t pkg_id = atoi(ent->d_name);
|
|
|
|
if (pkg_id != 0) {
|
|
pkginfo *ff = new pkginfo();
|
|
|
|
if (ff->open(type, pkg_id)) {
|
|
if (curr) curr->next = ff;
|
|
if (!first) first = ff;
|
|
curr = ff;
|
|
} else {
|
|
tms_errorf("could not open pkg %d", pkg_id);
|
|
delete ff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
lvlfile *pkgman::get_levels(int level_type) {
|
|
bool state = false;
|
|
|
|
int orig_level_type = level_type;
|
|
|
|
if (level_type >= LEVEL_LOCAL_STATE) {
|
|
level_type -= LEVEL_LOCAL_STATE;
|
|
state = true;
|
|
}
|
|
|
|
if (level_type < 0 || level_type >= 5) {
|
|
tms_warnf("unknown level type");
|
|
return 0;
|
|
}
|
|
|
|
char ext[6];
|
|
snprintf(ext, 6, ".%s", pkgman::get_level_ext(orig_level_type));
|
|
const char *path = pkgman::get_level_path(orig_level_type);
|
|
|
|
char tmp[1024];
|
|
|
|
DIR *dir;
|
|
struct dirent *ent;
|
|
lvlfile *first = 0, *last = 0;
|
|
|
|
dir = opendir(path);
|
|
|
|
if (!dir) {
|
|
tms_errorf("could not open directory %s", path);
|
|
return first;
|
|
}
|
|
|
|
while ((ent = readdir(dir))) {
|
|
int len = strlen(ent->d_name);
|
|
|
|
if (!(len > 5 && memcmp(&ent->d_name[len-5], ext, 5) == 0))
|
|
continue;
|
|
|
|
uint32_t level_id = 0;
|
|
uint32_t save_id = 0;
|
|
uint8_t state_level_type = LEVEL_LOCAL_STATE;
|
|
|
|
if (state) {
|
|
// State format: {0:s:type}.{1:d:id}.{2:d:save_id}.{3:s:ext}
|
|
if (ent->d_name[0] == 'l')
|
|
state_level_type = LEVEL_LOCAL_STATE;
|
|
else if (ent->d_name[0] == 'd')
|
|
state_level_type = LEVEL_DB_STATE;
|
|
|
|
char *level_id_c = strchr(ent->d_name, '.');
|
|
level_id = atoi(level_id_c+1);
|
|
save_id = atoi(strchr(level_id_c+1, '.')+1);
|
|
} else {
|
|
// Regular level format: {0:d:id}.{1:s:save_id}
|
|
level_id = atoi(ent->d_name);
|
|
|
|
save_id = atoi(strchr(ent->d_name, '.')+1);
|
|
}
|
|
|
|
snprintf(tmp, 1023, "%s/%s", path, ent->d_name);
|
|
time_t mtime = get_mtime(tmp);
|
|
|
|
char date[21];
|
|
strftime(date, 20, "%Y-%m-%d %H:%M:%S", gmtime((time_t*)&(mtime)));
|
|
|
|
if (!(level_id != 0 || state))
|
|
continue;
|
|
|
|
lvlfile *ff = new lvlfile((state ? state_level_type : level_type), level_id);
|
|
strcpy(ff->modified_date, date);
|
|
ff->mtime = mtime;
|
|
ff->save_id = save_id;
|
|
|
|
/**
|
|
* For state searches, we do not care about the original level type.
|
|
* The original level type is merely there to notify us that it's states
|
|
* we are looking for.
|
|
* The level type of the current state is discerned via the filename
|
|
* using the first "dotted" argument (level or db).
|
|
**/
|
|
if (!pkgman::get_level_data((state ? state_level_type : orig_level_type), level_id, save_id, ff->name, &ff->version)) {
|
|
tms_warnf("Unable to get level name for lid %u", level_id);
|
|
delete ff;
|
|
continue;
|
|
}
|
|
|
|
if (!first) {
|
|
first = ff;
|
|
} else {
|
|
// loop through and insert the lvlfile as soon as the date is newer
|
|
|
|
lvlfile *l = first, *prev = 0;
|
|
while (l) {
|
|
if (strcmp(ff->modified_date, l->modified_date) > 0) {
|
|
break;
|
|
}
|
|
prev = l;
|
|
l = l->next;
|
|
}
|
|
|
|
if (!prev) {
|
|
ff->next = first;
|
|
first = ff;
|
|
} else {
|
|
ff->next = prev->next;
|
|
prev->next = ff;
|
|
}
|
|
}
|
|
}
|
|
closedir(dir);
|
|
|
|
return first;
|
|
}
|
|
|
|
uint32_t pkgman::get_latest_level_id(int level_type) {
|
|
lvlfile *level = pkgman::get_levels(LEVEL_LOCAL);
|
|
|
|
uint32_t latest_id = 0;
|
|
time_t latest_mtime = 0;
|
|
|
|
while (level) {
|
|
if (level->mtime > latest_mtime) {
|
|
latest_id = level->id;
|
|
latest_mtime = level->mtime;
|
|
}
|
|
|
|
lvlfile *next = level->next;
|
|
delete level;
|
|
level = next;
|
|
}
|
|
|
|
return latest_id;
|
|
}
|
|
|
|
extern "C" struct lvlfile* pkgman_get_levels(int level_type) {
|
|
return pkgman::get_levels(level_type);
|
|
}
|
|
|
|
// lvledit
|
|
|
|
bool lvledit::open(int lvl_type, uint32_t lvl_id) {
|
|
char filename[1024];
|
|
|
|
const char *ext = pkgman::get_level_ext(lvl_type);
|
|
const char *path = pkgman::get_level_path(lvl_type);
|
|
|
|
if (lvl_id == 0)
|
|
snprintf(filename, 1024, "%s/.autosave", path);
|
|
else
|
|
snprintf(filename, 1024, "%s/%d.%s", path, lvl_id, ext);
|
|
|
|
this->lvl_type = 0;
|
|
this->lvl_id = 0;
|
|
|
|
if (!open_from_path(filename)) {
|
|
tms_errorf("could not open level file '%s'", filename);
|
|
return false;
|
|
}
|
|
|
|
this->lvl_type = lvl_type;
|
|
this->lvl_id = lvl_id;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool lvledit::save() {
|
|
char filename[1024];
|
|
|
|
const char *ext = pkgman::get_level_ext(lvl_type);
|
|
const char *path = pkgman::get_level_path(lvl_type);
|
|
|
|
snprintf(filename, 1024, "%s/%d.%s", path, this->lvl_id, ext);
|
|
|
|
return save_to_path(filename);
|
|
}
|
|
|
|
bool lvledit::open_from_path(const char *path) {
|
|
FILE *fp = fopen(path, "rb");
|
|
|
|
if (!fp) {
|
|
tms_errorf("could not open file '%s'", path);
|
|
return false;
|
|
}
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
long size = ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
if (size > 2*1024*1024) {
|
|
tms_fatalf("file too big");
|
|
}
|
|
|
|
this->lb.reset();
|
|
this->lb.size = 0;
|
|
this->lb.ensure((int)size);
|
|
|
|
fread(this->lb.buf, 1, size, fp);
|
|
|
|
fclose(fp);
|
|
|
|
this->lb.size = size;
|
|
this->lvl.read(&this->lb);
|
|
this->header_size = this->lvl.get_size();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool lvledit::save_to_path(const char *path) {
|
|
if (this->lvl.get_size() != this->header_size) {
|
|
// new header size does not match old header size,
|
|
// we need to perform a memmove on the object data
|
|
|
|
int diff = this->lvl.get_size() - this->header_size;
|
|
|
|
if (diff > 0)
|
|
this->lb.ensure(diff);
|
|
|
|
char *header_end = (char*)this->lb.buf + this->header_size;
|
|
memmove(header_end + diff, header_end, this->lb.size - this->header_size);
|
|
|
|
this->header_size += diff;
|
|
this->lb.size += diff;
|
|
}
|
|
|
|
int saved_size = this->lb.size;
|
|
this->lb.size = 0;
|
|
this->lvl.write(&this->lb);
|
|
this->lb.size = saved_size;
|
|
|
|
FILE *fp = fopen(path, "wb");
|
|
|
|
if (!fp) {
|
|
tms_errorf("could not open file '%s' for writing", path);
|
|
return false;
|
|
}
|
|
|
|
fwrite(this->lb.buf, 1, this->lb.size, fp);
|
|
fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
void lvledit::print_gids() {
|
|
// IMPORTANT: keep this in sync with of::read()
|
|
this->lb.rp = this->lvl.get_size();
|
|
|
|
uint32_t num_groups = this->lvl.num_groups;
|
|
uint32_t num_entities = this->lvl.num_entities;
|
|
|
|
// skip groups
|
|
#if 0
|
|
this->lb.rp +=
|
|
num_groups *
|
|
(
|
|
sizeof(uint32_t) // id
|
|
+sizeof(float) // pos.x
|
|
+sizeof(float) // pos.y
|
|
+sizeof(float) // angle
|
|
+(version >= LEVEL_VERSION_1_5 ? sizeof(uint32_t) : 0)
|
|
);
|
|
#endif
|
|
for (uint32_t x = 0; x < num_groups; ++x) {
|
|
lb.r_uint32(); // id
|
|
lb.rp += sizeof(float) // pos.x
|
|
+ sizeof(float) // pos.y
|
|
+ sizeof(float);// angle
|
|
|
|
if (this->lvl.version >= LEVEL_VERSION_1_5) {
|
|
uint32_t state_size = lb.r_uint32();
|
|
lb.rp += state_size;
|
|
}
|
|
}
|
|
|
|
for (uint32_t x = 0; x < num_entities; ++x) {
|
|
uint8_t np;
|
|
uint8_t nc = 0;
|
|
uint32_t state_size = 0;
|
|
|
|
uint8_t g_id = lb.r_uint8(); // g_id
|
|
lb.r_uint32(); // id
|
|
lb.r_uint16(); // group_id half
|
|
lb.r_uint16(); // group id half
|
|
|
|
printf("%s%u",((x>0)?"\n":""), g_id); // g_id
|
|
|
|
np = lb.r_uint8(); // num properties
|
|
|
|
if (this->lvl.version >= LEVEL_VERSION_1_5) {
|
|
nc = lb.r_uint8(); // num chunks
|
|
state_size = lb.r_uint32(); // state size
|
|
}
|
|
|
|
(void)lb.r_float(); // pos.x
|
|
(void)lb.r_float(); // pos.y
|
|
(void)lb.r_float(); // angle
|
|
(void)lb.r_uint8(); // layer
|
|
|
|
if (this->lvl.version >= LEVEL_VERSION_1_5) {
|
|
lb.r_uint64(); // state flags
|
|
// this includes moveable and axisrot
|
|
|
|
for (int x=0; x<nc; x++) {
|
|
lb.r_int32(); // chunk x
|
|
lb.r_int32(); // chunk y
|
|
}
|
|
|
|
// skip state buffer
|
|
lb.rp += state_size;
|
|
} else {
|
|
(void)lb.r_uint8(); // axisrot
|
|
if (this->lvl.version >= 10) {
|
|
(void)lb.r_uint8(); // moveable
|
|
}
|
|
}
|
|
|
|
for (int x=0; x<np; x++) {
|
|
uint8_t type = lb.r_uint8();
|
|
|
|
switch (type) {
|
|
case P_INT8:
|
|
lb.r_uint8();
|
|
break;
|
|
|
|
case P_INT:
|
|
case P_ID:
|
|
lb.r_uint32();
|
|
break;
|
|
|
|
case P_FLT:
|
|
lb.r_float();
|
|
break;
|
|
|
|
case P_STR:
|
|
if (this->lvl.version >= LEVEL_VERSION_1_5) {
|
|
uint32_t len = lb.r_uint32();
|
|
lb.rp += len;
|
|
} else {
|
|
uint16_t len = lb.r_uint16();
|
|
lb.rp += len;
|
|
}
|
|
break;
|
|
|
|
default: fprintf(stderr, "invalid object property %d", type); exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// lvlbuf
|
|
|
|
void lvlbuf::ensure(uint64_t s) {
|
|
uint64_t ns = this->size + s;
|
|
if (ns > this->cap) {
|
|
uint64_t old_cap = this->cap;
|
|
if (this->sparse_resize) {
|
|
// round up to nearest power of two
|
|
this->cap = ns;
|
|
this->cap --;
|
|
this->cap |= this->cap >> 1;
|
|
this->cap |= this->cap >> 2;
|
|
this->cap |= this->cap >> 4;
|
|
this->cap |= this->cap >> 8;
|
|
this->cap |= this->cap >> 16;
|
|
this->cap++;
|
|
} else {
|
|
this->cap = ((ns > this->cap+4096) ? ns+4096 : this->cap+4096);
|
|
}
|
|
|
|
this->buf = static_cast<uint8_t*>(realloc(this->buf, this->cap));
|
|
}
|
|
}
|
|
|
|
void lvlbuf::zcompress(const lvlinfo &level, unsigned char **dest, uint64_t *dest_len) const {
|
|
uint64_t header_size = level.get_size();
|
|
|
|
*dest_len = compressBound(this->size-header_size);
|
|
*dest = (unsigned char*)malloc(*dest_len);
|
|
|
|
int ret = compress(*dest, (uLong*)dest_len, this->buf+header_size, this->size-header_size);
|
|
|
|
// TODO: Better error-handling
|
|
switch (ret) {
|
|
case Z_OK:
|
|
tms_infof("zcompress success");
|
|
break;
|
|
|
|
case Z_MEM_ERROR:
|
|
tms_errorf("no mem");
|
|
break;
|
|
|
|
case Z_BUF_ERROR:
|
|
tms_errorf("not enough room in buffer");
|
|
break;
|
|
|
|
case Z_STREAM_ERROR:
|
|
tms_errorf("z stream error");
|
|
break;
|
|
|
|
default:
|
|
tms_infof("unknown ret: %d", ret);
|
|
break;
|
|
}
|
|
|
|
tms_infof("Compression status: Old: %" PRIu64 ". New: %" PRIu64 ". Total: %.2f", this->size-header_size, *dest_len, (float)(float)*dest_len/((this->size-header_size)));
|
|
}
|
|
|
|
void lvlbuf::zuncompress(const lvlinfo &level) {
|
|
uint64_t header_size = level.get_size();
|
|
|
|
uLong dest_len = level.compression_length;
|
|
unsigned char *dest = (unsigned char*)malloc(dest_len);
|
|
|
|
int ret = uncompress(dest, &dest_len, this->buf+header_size, this->size-header_size);
|
|
|
|
// TODO: Better error-handling
|
|
switch (ret) {
|
|
case Z_OK:
|
|
tms_infof("zuncompress success");
|
|
break;
|
|
|
|
case Z_MEM_ERROR:
|
|
tms_errorf("no mem");
|
|
break;
|
|
|
|
case Z_BUF_ERROR:
|
|
tms_errorf("not enough room in buffer");
|
|
break;
|
|
|
|
case Z_STREAM_ERROR:
|
|
tms_errorf("z stream error");
|
|
break;
|
|
|
|
default:
|
|
tms_infof("unknown ret: %d", ret);
|
|
break;
|
|
}
|
|
|
|
tms_infof("Uncompression status: Old: %" PRIu64 ". New: %u. Total: %.2f", this->size-header_size, (unsigned)dest_len, (float)(float)dest_len/((this->size-header_size)));
|
|
|
|
this->size = header_size;
|
|
this->ensure(dest_len);
|
|
this->size = header_size + dest_len;
|
|
|
|
memcpy(this->buf+header_size, dest, dest_len);
|
|
|
|
free(dest);
|
|
}
|