add named registers in quest assembler
This commit is contained in:
+298
-46
@@ -1775,6 +1775,186 @@ Episode episode_for_quest_episode_number(uint8_t episode_number) {
|
||||
}
|
||||
}
|
||||
|
||||
struct RegisterAssigner {
|
||||
struct Register {
|
||||
string name;
|
||||
int16_t number = -1; // -1 = unassigned (any number)
|
||||
shared_ptr<Register> prev;
|
||||
shared_ptr<Register> next;
|
||||
unordered_set<size_t> offsets;
|
||||
};
|
||||
|
||||
~RegisterAssigner() {
|
||||
for (auto& it : this->named_regs) {
|
||||
it.second->prev.reset();
|
||||
it.second->next.reset();
|
||||
}
|
||||
for (auto& reg : this->numbered_regs) {
|
||||
if (reg) {
|
||||
reg->prev.reset();
|
||||
reg->next.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Register> get_or_create(const string& name, int16_t number) {
|
||||
shared_ptr<Register> reg;
|
||||
try {
|
||||
if (!name.empty()) {
|
||||
reg = this->named_regs.at(name);
|
||||
} else if (number >= 0 && number < 0x100) {
|
||||
reg = this->numbered_regs.at(number);
|
||||
} else {
|
||||
throw runtime_error("invalid register name or number");
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
if (!reg) {
|
||||
reg = make_shared<Register>();
|
||||
}
|
||||
|
||||
if (number >= 0) {
|
||||
if (reg->number < 0) {
|
||||
reg->number = number;
|
||||
if (this->numbered_regs.at(reg->number)) {
|
||||
throw runtime_error(string_printf("number %hd is already assigned to a different register", reg->number));
|
||||
}
|
||||
this->numbered_regs.at(reg->number) = reg;
|
||||
} else if (reg->number != number) {
|
||||
throw runtime_error(string_printf("register %s is assigned multiple numbers", reg->name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.empty()) {
|
||||
if (reg->name.empty()) {
|
||||
reg->name = name;
|
||||
if (!this->named_regs.emplace(reg->name, reg).second) {
|
||||
throw runtime_error(string_printf("name %s is already assigned to a different register", reg->name.c_str()));
|
||||
}
|
||||
} else if (reg->name != name) {
|
||||
throw runtime_error(string_printf("register %hd is assigned multiple names", reg->number));
|
||||
}
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
void assign_number(shared_ptr<Register> reg, uint8_t number) {
|
||||
if (reg->number < 0) {
|
||||
reg->number = number;
|
||||
if (this->numbered_regs.at(reg->number)) {
|
||||
throw logic_error(string_printf("register number %hd assigned multiple times", reg->number));
|
||||
}
|
||||
this->numbered_regs.at(reg->number) = reg;
|
||||
} else if (reg->number != static_cast<int16_t>(number)) {
|
||||
throw runtime_error(string_printf("assigning different register number %hhu over existing register number %hd", number, reg->number));
|
||||
}
|
||||
}
|
||||
|
||||
void constrain(shared_ptr<Register> first_reg, shared_ptr<Register> second_reg) {
|
||||
if (!first_reg->next) {
|
||||
first_reg->next = second_reg;
|
||||
} else if (first_reg->next != second_reg) {
|
||||
throw runtime_error(string_printf("register %s must come after %s, but is already constrained to another register", second_reg->name.c_str(), first_reg->name.c_str()));
|
||||
}
|
||||
if (!second_reg->prev) {
|
||||
second_reg->prev = first_reg;
|
||||
} else if (second_reg->prev != first_reg) {
|
||||
throw runtime_error(string_printf("register %s must come before %s, but is already constrained to another register", first_reg->name.c_str(), second_reg->name.c_str()));
|
||||
}
|
||||
if ((first_reg->number >= 0) && (second_reg->number >= 0) && (first_reg->number != ((second_reg->number - 1) & 0xFF))) {
|
||||
throw runtime_error(string_printf("register %s must come before %s, but both registers already have non-consecutive numbers", first_reg->name.c_str(), second_reg->name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void assign_all() {
|
||||
// TODO: Technically, we should assign the biggest blocks first to minimize
|
||||
// fragmentation. I am lazy and haven't implemented this yet.
|
||||
unordered_set<shared_ptr<Register>> unassigned;
|
||||
for (auto it : this->named_regs) {
|
||||
if (it.second->number < 0) {
|
||||
unassigned.emplace(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
while (!unassigned.empty()) {
|
||||
auto reg_it = unassigned.begin();
|
||||
auto reg = *reg_it;
|
||||
unassigned.erase(reg_it);
|
||||
|
||||
// If this register is already assigned, skip it
|
||||
if (reg->number >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If any next register is assigned, assign this register
|
||||
size_t next_delta = 1;
|
||||
for (auto next_reg = reg->next; next_reg; next_reg = next_reg->next, next_delta++) {
|
||||
if (next_reg->number >= 0) {
|
||||
this->assign_number(reg, (next_reg->number - next_delta) & 0xFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reg->number >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If any prev register is assigned, assign this register
|
||||
size_t prev_delta = 1;
|
||||
for (auto prev_reg = reg->prev; prev_reg; prev_reg = prev_reg->prev, prev_delta++) {
|
||||
if (prev_reg->number >= 0) {
|
||||
this->assign_number(reg, (prev_reg->number + prev_delta) & 0xFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reg->number >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// No prev or next register is assigned; find an interval in the register
|
||||
// number space that fits this block of registers. The total number of
|
||||
// register numbers needed is (prev_delta - 1) + (next_delta - 1) + 1.
|
||||
size_t num_regs = prev_delta + next_delta - 1;
|
||||
this->assign_number(reg, (this->find_register_number_space(num_regs) + (prev_delta - 1)) & 0xFF);
|
||||
|
||||
// We don't need to assign the prev and next registers; they should also
|
||||
// be in the unassigned set and will be assigned by the above logic
|
||||
}
|
||||
|
||||
// At this point, all registers should be assigned
|
||||
for (const auto& it : this->named_regs) {
|
||||
if (it.second->number < 0) {
|
||||
throw logic_error(string_printf("register %s was not assigned", it.second->name.c_str()));
|
||||
}
|
||||
}
|
||||
for (size_t z = 0; z < 0x100; z++) {
|
||||
auto reg = this->numbered_regs[z];
|
||||
if (reg && (reg->number != static_cast<int16_t>(z))) {
|
||||
throw logic_error(string_printf("register %zu has incorrect number %hd", z, reg->number));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t find_register_number_space(size_t num_regs) const {
|
||||
for (size_t candidate = 0; candidate < 0x100; candidate++) {
|
||||
size_t z;
|
||||
for (z = 0; z < num_regs; z++) {
|
||||
if (this->numbered_regs[candidate + z]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (z == num_regs) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw runtime_error("not enough space to assign registers");
|
||||
}
|
||||
|
||||
unordered_map<string, shared_ptr<Register>> named_regs;
|
||||
array<shared_ptr<Register>, 0x100> numbered_regs;
|
||||
};
|
||||
|
||||
std::string assemble_quest_script(const std::string& text) {
|
||||
auto lines = split(text, '\n');
|
||||
|
||||
@@ -1901,6 +2081,88 @@ std::string assemble_quest_script(const std::string& text) {
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare to collect named registers
|
||||
RegisterAssigner reg_assigner;
|
||||
auto parse_reg = [®_assigner](const string& arg, bool allow_unnumbered = true) -> shared_ptr<RegisterAssigner::Register> {
|
||||
if (arg.size() < 2) {
|
||||
throw runtime_error("register argument is too short");
|
||||
}
|
||||
if ((arg[0] != 'r') && (arg[0] != 'f')) {
|
||||
throw runtime_error("a register is required");
|
||||
}
|
||||
string name;
|
||||
ssize_t number = -1;
|
||||
if (arg[1] == ':') {
|
||||
auto tokens = split(arg.substr(2), '@');
|
||||
if (tokens.size() == 1) {
|
||||
name = std::move(tokens[0]);
|
||||
} else if (tokens.size() == 2) {
|
||||
name = std::move(tokens[0]);
|
||||
number = stoull(tokens[1], nullptr, 0);
|
||||
} else {
|
||||
throw runtime_error("invalid register specification");
|
||||
}
|
||||
} else {
|
||||
number = stoull(arg.substr(1), nullptr, 0);
|
||||
}
|
||||
if (!allow_unnumbered && (number < 0)) {
|
||||
throw runtime_error("a numbered register is required");
|
||||
}
|
||||
if (number > 0xFF) {
|
||||
throw runtime_error("invalid register number");
|
||||
}
|
||||
return reg_assigner.get_or_create(name, number);
|
||||
};
|
||||
auto parse_reg_set_fixed = [®_assigner, &parse_reg](const string& name, size_t expected_count) -> vector<shared_ptr<RegisterAssigner::Register>> {
|
||||
if (expected_count == 0) {
|
||||
throw logic_error("REG_SET_FIXED argument expects no registers");
|
||||
}
|
||||
if (name.empty()) {
|
||||
throw runtime_error("no register specified for REG_SET_FIXED argument");
|
||||
}
|
||||
vector<shared_ptr<RegisterAssigner::Register>> regs;
|
||||
if ((name[0] == '(') && (name.back() == ')')) {
|
||||
auto tokens = split(name.substr(1, name.size() - 2), ',');
|
||||
if (tokens.size() != expected_count) {
|
||||
throw runtime_error("incorrect number of registers in REG_SET_FIXED");
|
||||
}
|
||||
for (auto& token : tokens) {
|
||||
strip_trailing_whitespace(token);
|
||||
strip_leading_whitespace(token);
|
||||
regs.emplace_back(parse_reg(token));
|
||||
if (regs.size() > 1) {
|
||||
reg_assigner.constrain(regs.at(regs.size() - 2), regs.back());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto tokens = split(name, '-');
|
||||
if (tokens.size() == 1) {
|
||||
regs.emplace_back(parse_reg(tokens[0], false));
|
||||
while (regs.size() < expected_count) {
|
||||
regs.emplace_back(parse_reg("", (regs.back()->number + 1) & 0xFF));
|
||||
reg_assigner.constrain(regs.at(regs.size() - 2), regs.back());
|
||||
}
|
||||
} else if (tokens.size() == 2) {
|
||||
regs.emplace_back(parse_reg(tokens[0], false));
|
||||
while (regs.size() < expected_count - 1) {
|
||||
regs.emplace_back(reg_assigner.get_or_create("", (regs.back()->number + 1) & 0xFF));
|
||||
reg_assigner.constrain(regs.at(regs.size() - 2), regs.back());
|
||||
}
|
||||
regs.emplace_back(parse_reg(tokens[1], false));
|
||||
if (static_cast<size_t>(regs.back()->number - regs.front()->number + 1) != expected_count) {
|
||||
throw runtime_error("incorrect number of registers used");
|
||||
}
|
||||
reg_assigner.constrain(regs.at(regs.size() - 2), regs.back());
|
||||
} else {
|
||||
throw runtime_error("invalid fixed register set syntax");
|
||||
}
|
||||
}
|
||||
if (regs.empty() || regs.size() != expected_count) {
|
||||
throw logic_error("incorrect register count in REG_SET_FIXED after parsing");
|
||||
}
|
||||
return regs;
|
||||
};
|
||||
|
||||
// Assemble code segment
|
||||
bool version_has_args = F_HAS_ARGS & v_flag(quest_version);
|
||||
const auto& opcodes = opcodes_by_name_for_version(quest_version);
|
||||
@@ -1974,16 +2236,6 @@ std::string assemble_quest_script(const std::string& text) {
|
||||
strip_leading_whitespace(arg);
|
||||
|
||||
try {
|
||||
auto parse_reg = +[](const string& name) -> uint8_t {
|
||||
if ((name[0] != 'r') && (name[0] != 'f')) {
|
||||
throw runtime_error("a register is required");
|
||||
}
|
||||
size_t reg_num = stoull(name.substr(1), nullptr, 0);
|
||||
if (reg_num > 0xFF) {
|
||||
throw runtime_error("invalid register number");
|
||||
}
|
||||
return reg_num;
|
||||
};
|
||||
auto add_cstr = [&](const string& text) -> void {
|
||||
switch (quest_version) {
|
||||
case Version::DC_NTE:
|
||||
@@ -2019,37 +2271,33 @@ std::string assemble_quest_script(const std::string& text) {
|
||||
} else if (label_it != labels_by_name.end()) {
|
||||
code_w.put_u8(0x4B); // arg_pushw
|
||||
code_w.put_u16l(label_it->second->index);
|
||||
} else if ((arg[0] == 'r') || (arg[0] == 'f')) {
|
||||
} else if ((arg[0] == 'r') || (arg[0] == 'f') || ((arg[0] == '(') && (arg.back() == ')'))) {
|
||||
// If the corresponding argument is a REG or REG_SET_FIXED, push
|
||||
// the register number, not the register's value, since it's an
|
||||
// out-param
|
||||
if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) {
|
||||
code_w.put_u8(0x4A); // arg_pushb
|
||||
code_w.put_u8(parse_reg(arg));
|
||||
auto reg = parse_reg(arg);
|
||||
reg->offsets.emplace(code_w.size());
|
||||
code_w.put_u8(reg->number);
|
||||
} else if (
|
||||
(arg_def.type == Type::REG_SET_FIXED) ||
|
||||
(arg_def.type == Type::REG32_SET_FIXED)) {
|
||||
auto tokens = split(arg, '-');
|
||||
uint8_t start_reg;
|
||||
if (tokens.size() == 1) {
|
||||
start_reg = parse_reg(tokens[0]);
|
||||
} else if (tokens.size() == 2) {
|
||||
start_reg = parse_reg(tokens[0]);
|
||||
if (static_cast<size_t>(parse_reg(tokens[1]) - start_reg + 1) != arg_def.count) {
|
||||
throw runtime_error("incorrect number of registers used");
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("invalid fixed register set syntax");
|
||||
}
|
||||
auto regs = parse_reg_set_fixed(arg, arg_def.count);
|
||||
code_w.put_u8(0x4A); // arg_pushb
|
||||
code_w.put_u8(start_reg);
|
||||
regs[0]->offsets.emplace(code_w.size());
|
||||
code_w.put_u8(regs[0]->number);
|
||||
} else {
|
||||
code_w.put_u8(0x48); // arg_pushr
|
||||
code_w.put_u8(parse_reg(arg));
|
||||
auto reg = parse_reg(arg);
|
||||
reg->offsets.emplace(code_w.size());
|
||||
code_w.put_u8(reg->number);
|
||||
}
|
||||
} else if ((arg[0] == '@') && ((arg[1] == 'r') || (arg[1] == 'f'))) {
|
||||
code_w.put_u8(0x4C); // arg_pusha
|
||||
code_w.put_u8(parse_reg(arg.substr(1)));
|
||||
auto reg = parse_reg(arg.substr(1));
|
||||
reg->offsets.emplace(code_w.size());
|
||||
code_w.put_u8(reg->number);
|
||||
} else if ((arg[0] == '@') && labels_by_name.count(arg.substr(1))) {
|
||||
code_w.put_u8(0x4D); // arg_pusho
|
||||
code_w.put_u16(labels_by_name.at(arg.substr(1))->index);
|
||||
@@ -2094,11 +2342,12 @@ std::string assemble_quest_script(const std::string& text) {
|
||||
code_w.put_u16(labels_by_name.at(name)->index);
|
||||
}
|
||||
};
|
||||
auto add_reg = [&](const string& name, bool is32) -> void {
|
||||
auto add_reg = [&](shared_ptr<RegisterAssigner::Register> reg, bool is32) -> void {
|
||||
reg->offsets.emplace(code_w.size());
|
||||
if (is32) {
|
||||
code_w.put_u32l(parse_reg(name));
|
||||
code_w.put_u32l(reg->number & 0xFF);
|
||||
} else {
|
||||
code_w.put_u8(parse_reg(name));
|
||||
code_w.put_u8(reg->number);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2130,30 +2379,21 @@ std::string assemble_quest_script(const std::string& text) {
|
||||
}
|
||||
case Type::REG:
|
||||
case Type::REG32:
|
||||
add_reg(arg, arg_def.type == Type::REG32);
|
||||
add_reg(parse_reg(arg), arg_def.type == Type::REG32);
|
||||
break;
|
||||
case Type::REG_SET_FIXED:
|
||||
case Type::REG32_SET_FIXED: {
|
||||
auto tokens = split(arg, '-');
|
||||
if (tokens.size() == 1) {
|
||||
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
|
||||
} else if (tokens.size() == 2) {
|
||||
if (static_cast<size_t>(parse_reg(tokens[1]) - parse_reg(tokens[0]) + 1) != arg_def.count) {
|
||||
throw runtime_error("incorrect number of registers used");
|
||||
}
|
||||
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
|
||||
} else {
|
||||
throw runtime_error("invalid fixed register set syntax");
|
||||
}
|
||||
auto regs = parse_reg_set_fixed(arg, arg_def.count);
|
||||
add_reg(regs[0], arg_def.type == Type::REG32_SET_FIXED);
|
||||
break;
|
||||
}
|
||||
case Type::REG_SET: {
|
||||
auto regs = split_set(arg);
|
||||
code_w.put_u8(regs.size());
|
||||
for (auto reg : regs) {
|
||||
strip_trailing_whitespace(reg);
|
||||
strip_leading_whitespace(reg);
|
||||
add_reg(reg, false);
|
||||
for (auto reg_arg : regs) {
|
||||
strip_trailing_whitespace(reg_arg);
|
||||
strip_leading_whitespace(reg_arg);
|
||||
add_reg(parse_reg(reg_arg), false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2198,6 +2438,18 @@ std::string assemble_quest_script(const std::string& text) {
|
||||
code_w.put_u8(0);
|
||||
}
|
||||
|
||||
// Assign all register numbers and patch the code section if needed
|
||||
reg_assigner.assign_all();
|
||||
for (size_t z = 0; z < 0x100; z++) {
|
||||
auto reg = reg_assigner.numbered_regs[z];
|
||||
if (!reg) {
|
||||
continue;
|
||||
}
|
||||
for (size_t offset : reg->offsets) {
|
||||
code_w.pput_u8(offset, reg->number);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate function table
|
||||
ssize_t function_table_size = labels_by_index.rbegin()->first + 1;
|
||||
vector<le_uint32_t> function_table;
|
||||
|
||||
@@ -59,15 +59,38 @@
|
||||
// capability.
|
||||
// .joinable
|
||||
|
||||
// The quest script begins here. A quest script is a sequence of opcodes, and
|
||||
// labels denoting positions within that sequence that can be jumped to or
|
||||
// called like a function. All labels have names, and some have numbers. (In the
|
||||
// compiled format, labels have only numbers and no names; during compilation,
|
||||
// each label that doesn't have a number is assigned a number that isn't in use
|
||||
// by another label.) To explicitly specify a label number (for example, if an
|
||||
// object or NPC refers to a label by number), use an @ sign followed by the
|
||||
// desired number. Note that numbers can be specified in decimal or hexadecimal;
|
||||
// see on_talk_to_npc1 and on_talk_to_npc2 for examples.
|
||||
// The quest script begins after the header directives. A quest script is a
|
||||
// sequence of opcodes, and labels denoting positions within that sequence that
|
||||
// can be jumped to or called like a function. All labels have names, and some
|
||||
// have numbers. (In the compiled format, labels have only numbers and no names;
|
||||
// during compilation, each label that doesn't have a number is assigned a
|
||||
// number that isn't in use by another label.) To explicitly specify a label
|
||||
// number (for example, if an object or NPC refers to a label by number), use an
|
||||
// @ sign followed by the desired number. Note that numbers can be specified in
|
||||
// decimal or hexadecimal; see on_talk_to_npc1 and on_talk_to_npc2 for examples.
|
||||
|
||||
// Registers may be named as well as labels. (In the compiled script, registers
|
||||
// do not have names, so disassembling a quest script always produces only
|
||||
// numbered registers.) When compiling, all of the following are valid:
|
||||
// r83 (explicitly numbered register)
|
||||
// r:difficulty_level (the compiler will assign an unused register number)
|
||||
// r:difficulty_level@83 (named and explicitly numbered)
|
||||
// You don't always have to use the same form for each register; for example,
|
||||
// if you use r:difficulty_level@83 anywhere in the quest script, you can also
|
||||
// use r:difficulty_level and r83 in other places and they will all refer to the
|
||||
// same register. (However, if you don't use r:difficulty_level@83 anywhere, but
|
||||
// you do use r83 and r:difficulty_level, the compiler will assign these to two
|
||||
// different registers since there is nothing linking the name to the number.)
|
||||
|
||||
// Using opcodes that take a consecutive sequence of registers, such as
|
||||
// map_designate which takes 4, introduces constraints on which registers may be
|
||||
// assigned to which numbers. For example, before one of the map_designate
|
||||
// opcodes after the start label, we explicitly assign one register's number,
|
||||
// but leave the nearby registers' numbers unassigned. The compiler assigns
|
||||
// those four registers to r60-r63, because they are used in a map_designate
|
||||
// call. If we didn't explicitly number any of those registers, the compiler
|
||||
// would instead choose a consecutive sequence of register numbers that aren't
|
||||
// used anywhere else in the script.
|
||||
|
||||
// This quest does not contain any examples of non-script data, but such data
|
||||
// can be included in the quest script using the .data directive, like this:
|
||||
@@ -80,63 +103,63 @@
|
||||
// Every quest must have a start label; this is the main thread that starts when
|
||||
// the quest begins. The start label is always assigned number 0.
|
||||
start:
|
||||
gget 0x0091, r252
|
||||
gget 0x0091, r:flag_0091_value@252
|
||||
set_floor_handler 0, floor_handler_pioneer_2
|
||||
set_floor_handler 1, floor_handler_forest_1
|
||||
set_floor_handler 2, floor_handler_forest_2
|
||||
set_floor_handler 11, floor_handler_dragon
|
||||
set_qt_success on_quest_success
|
||||
get_difficulty_level_v2 r83
|
||||
leti r60, 0 // Pioneer 2
|
||||
leti r61, 0
|
||||
leti r62, 0
|
||||
leti r63, 0
|
||||
map_designate r60-r63
|
||||
leti r60, 1 // Forest 1
|
||||
leti r61, 0
|
||||
leti r62, 0
|
||||
leti r63, 0
|
||||
map_designate r60-r63
|
||||
leti r60, 2 // Forest 2
|
||||
leti r61, 0
|
||||
leti r62, 0
|
||||
leti r63, 0
|
||||
map_designate r60-r63
|
||||
leti r60, 11 // Dragon
|
||||
leti r61, 0
|
||||
leti r62, 0
|
||||
leti r63, 0
|
||||
map_designate r60-r63
|
||||
get_difficulty_level_v2 r:difficulty_level@83
|
||||
leti r:op_arg1, 0 // Pioneer 2
|
||||
leti r:op_arg2, 0
|
||||
leti r:op_arg3@62, 0 // See comment above about register assignment
|
||||
leti r:op_arg4, 0
|
||||
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
leti r:op_arg1, 1 // Forest 1
|
||||
leti r:op_arg2, 0
|
||||
leti r:op_arg3, 0
|
||||
leti r:op_arg4, 0
|
||||
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
leti r:op_arg1, 2 // Forest 2
|
||||
leti r:op_arg2, 0
|
||||
leti r:op_arg3, 0
|
||||
leti r:op_arg4, 0
|
||||
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
leti r:op_arg1, 11 // Dragon
|
||||
leti r:op_arg2, 0
|
||||
leti r:op_arg3, 0
|
||||
leti r:op_arg4, 0
|
||||
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
ret
|
||||
|
||||
return_immediately:
|
||||
ret
|
||||
|
||||
floor_handler_pioneer_2:
|
||||
switch_jmp r0, [floor_handler_pioneer_2_first_time, floor_handler_pioneer_2_not_first_time]
|
||||
switch_jmp r:has_talked_to_hopkins, [floor_handler_pioneer_2_first_time, floor_handler_pioneer_2_not_first_time]
|
||||
floor_handler_pioneer_2_first_time:
|
||||
set r50
|
||||
set_mainwarp 1
|
||||
leti r60, 0x000000ED
|
||||
leti r61, 0x00000000
|
||||
leti r62, 0x0000014D
|
||||
leti r63, 0xFFFFFFF1
|
||||
p_setpos 0, r60-r63
|
||||
leti r60, 0x000000FF
|
||||
leti r61, 0x00000000
|
||||
leti r62, 0x00000152
|
||||
leti r63, 0xFFFFFFD5
|
||||
p_setpos 1, r60-r63
|
||||
leti r60, 0x000000DE
|
||||
leti r61, 0x00000000
|
||||
leti r62, 0x00000142
|
||||
leti r63, 0x00000019
|
||||
p_setpos 2, r60-r63
|
||||
leti r60, 0x000000F8
|
||||
leti r61, 0x00000000
|
||||
leti r62, 0x00000143
|
||||
leti r63, 0xFFFFFFEC
|
||||
p_setpos 3, r60-r63
|
||||
leti r:op_arg1, 0x000000ED
|
||||
leti r:op_arg2, 0x00000000
|
||||
leti r:op_arg3, 0x0000014D
|
||||
leti r:op_arg4, 0xFFFFFFF1
|
||||
p_setpos 0, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
leti r:op_arg1, 0x000000FF
|
||||
leti r:op_arg2, 0x00000000
|
||||
leti r:op_arg3, 0x00000152
|
||||
leti r:op_arg4, 0xFFFFFFD5
|
||||
p_setpos 1, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
leti r:op_arg1, 0x000000DE
|
||||
leti r:op_arg2, 0x00000000
|
||||
leti r:op_arg3, 0x00000142
|
||||
leti r:op_arg4, 0x00000019
|
||||
p_setpos 2, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
leti r:op_arg1, 0x000000F8
|
||||
leti r:op_arg2, 0x00000000
|
||||
leti r:op_arg3, 0x00000143
|
||||
leti r:op_arg4, 0xFFFFFFEC
|
||||
p_setpos 3, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||
call on_talk_to_hopkins
|
||||
ret
|
||||
floor_handler_pioneer_2_not_first_time:
|
||||
@@ -156,10 +179,10 @@ label00CB@0x00CB:
|
||||
ret
|
||||
|
||||
on_quest_success:
|
||||
jmpi_eq r83, 0, on_quest_success_normal
|
||||
jmpi_eq r83, 1, on_quest_success_hard
|
||||
jmpi_eq r83, 2, on_quest_success_very_hard
|
||||
jmpi_eq r83, 3, on_quest_success_ultimate
|
||||
jmpi_eq r:difficulty_level, 0, on_quest_success_normal
|
||||
jmpi_eq r:difficulty_level, 1, on_quest_success_hard
|
||||
jmpi_eq r:difficulty_level, 2, on_quest_success_very_hard
|
||||
jmpi_eq r:difficulty_level, 3, on_quest_success_ultimate
|
||||
on_quest_success_normal:
|
||||
window_msg "You\'ve been awarded\n100 Meseta."
|
||||
bgm 1
|
||||
@@ -241,7 +264,7 @@ play_dragon_killed_cutscene_when_ready_check_again:
|
||||
ret
|
||||
|
||||
on_dragon_killed:
|
||||
jmpi_eq r83, 3, on_dragon_killed_ultimate
|
||||
jmpi_eq r:difficulty_level, 3, on_dragon_killed_ultimate
|
||||
window_msg "Dragon killed!"
|
||||
add_msg "Hopkins\'s HEAT SWORD found\nin Dragon\'s mouth!"
|
||||
se 1
|
||||
@@ -281,7 +304,7 @@ show_mission_complete_if_needed_check_again:
|
||||
on_talk_to_hopkins@310:
|
||||
jmpi_eq r255, 1, on_talk_to_hopkins_complete_again
|
||||
jmpi_eq r254, 1, on_talk_to_hopkins_complete
|
||||
jmpi_eq r0, 1, on_talk_to_hopkins_incomplete_again
|
||||
jmpi_eq r:has_talked_to_hopkins, 1, on_talk_to_hopkins_incomplete_again
|
||||
call start_cutscene
|
||||
call wait_30_frames
|
||||
message 100, "Ca... can you help\nme? Please?"
|
||||
@@ -290,7 +313,7 @@ on_talk_to_hopkins@310:
|
||||
add_msg "My HEAT SWORD...\nMy dad gave HEAT SWORD\nto me..."
|
||||
add_msg "It\'s really important to\nme. I don\'t know how it\nwas taken from me."
|
||||
add_msg "I cannot do my job\nwithout it! Please\nget it back for me."
|
||||
set r0
|
||||
set r:has_talked_to_hopkins
|
||||
mesend
|
||||
bgm 1
|
||||
call end_cutscene
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
EXECUTABLE="$1"
|
||||
if [ "$EXECUTABLE" == "" ]; then
|
||||
EXECUTABLE="./newserv"
|
||||
fi
|
||||
|
||||
echo "... assemble system/quests/retrieval/q058-gc-e.bin.txt"
|
||||
$EXECUTABLE assemble-quest-script system/quests/retrieval/q058-gc-e.bin.txt tests/q058-gc-e-test.bin
|
||||
diff tests/q058-gc-e-test.bin tests/q058-gc-e.bin
|
||||
|
||||
echo "... clean up"
|
||||
rm tests/q058-gc-e-test.bin
|
||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user