2010-05-12 07:27:51 -06:00
|
|
|
/*
|
2011-06-16 15:53:39 -06:00
|
|
|
https://github.com/peterix/dfhack
|
2012-09-29 20:03:37 -06:00
|
|
|
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
2010-05-12 07:27:51 -06:00
|
|
|
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
|
|
warranty. In no event will the authors be held liable for any
|
|
|
|
damages arising from the use of this software.
|
|
|
|
|
|
|
|
Permission is granted to anyone to use this software for any
|
|
|
|
purpose, including commercial applications, and to alter it and
|
|
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
|
|
|
|
1. The origin of this software must not be misrepresented; you must
|
|
|
|
not claim that you wrote the original software. If you use this
|
|
|
|
software in a product, an acknowledgment in the product documentation
|
|
|
|
would be appreciated but is not required.
|
|
|
|
|
|
|
|
2. Altered source versions must be plainly marked as such, and
|
|
|
|
must not be misrepresented as being the original software.
|
|
|
|
|
|
|
|
3. This notice may not be removed or altered from any source
|
|
|
|
distribution.
|
|
|
|
*/
|
|
|
|
|
2011-06-16 15:53:39 -06:00
|
|
|
|
2010-05-26 04:24:45 -06:00
|
|
|
#include "Internal.h"
|
2018-08-26 17:26:09 -06:00
|
|
|
#include <array>
|
2011-04-10 02:19:15 -06:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
#include <map>
|
2022-08-28 19:08:40 -06:00
|
|
|
#include <unordered_set>
|
2011-04-10 02:19:15 -06:00
|
|
|
#include <cstring>
|
|
|
|
using namespace std;
|
|
|
|
|
2011-12-31 04:48:42 -07:00
|
|
|
#include "modules/World.h"
|
|
|
|
#include "MemAccess.h"
|
|
|
|
#include "VersionInfo.h"
|
|
|
|
#include "Types.h"
|
|
|
|
#include "Error.h"
|
2011-03-18 01:53:59 -06:00
|
|
|
#include "ModuleFactory.h"
|
2011-12-31 04:48:42 -07:00
|
|
|
#include "Core.h"
|
2010-05-12 07:27:51 -06:00
|
|
|
|
2012-10-11 07:34:34 -06:00
|
|
|
#include "modules/Maps.h"
|
|
|
|
|
2012-01-07 08:21:31 -07:00
|
|
|
#include "MiscUtils.h"
|
|
|
|
|
2013-11-07 01:27:53 -07:00
|
|
|
#include "VTableInterpose.h"
|
|
|
|
|
2012-01-07 08:21:31 -07:00
|
|
|
#include "DataDefs.h"
|
|
|
|
#include "df/world.h"
|
|
|
|
#include "df/historical_figure.h"
|
2012-10-11 07:34:34 -06:00
|
|
|
#include "df/map_block.h"
|
|
|
|
#include "df/block_square_event_world_constructionst.h"
|
2013-11-07 01:27:53 -07:00
|
|
|
#include "df/viewscreen_legendsst.h"
|
2022-08-28 19:08:40 -06:00
|
|
|
#include "df/d_init.h"
|
|
|
|
#include "df/viewscreen_dwarfmodest.h"
|
|
|
|
#include "df/ui.h"
|
|
|
|
#include "VTableInterpose.h"
|
|
|
|
#include "PluginManager.h"
|
2012-01-07 08:21:31 -07:00
|
|
|
|
2010-05-12 07:27:51 -06:00
|
|
|
using namespace DFHack;
|
2022-08-28 19:08:40 -06:00
|
|
|
using namespace Pausing;
|
2012-10-11 07:34:34 -06:00
|
|
|
using namespace df::enums;
|
2010-05-12 07:27:51 -06:00
|
|
|
|
2012-01-24 20:51:17 -07:00
|
|
|
using df::global::world;
|
|
|
|
|
2011-03-18 04:09:26 -06:00
|
|
|
bool World::ReadPauseState()
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
return DF_GLOBAL_VALUE(pause_state, false);
|
2011-03-18 04:09:26 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void World::SetPauseState(bool paused)
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
bool dummy;
|
|
|
|
DF_GLOBAL_VALUE(pause_state, dummy) = paused;
|
2011-03-18 04:09:26 -06:00
|
|
|
}
|
|
|
|
|
2022-08-28 19:08:40 -06:00
|
|
|
std::unordered_set<Lock*> PlayerLock::locks;
|
|
|
|
std::unordered_set<Lock*> AnnouncementLock::locks;
|
|
|
|
|
2022-09-02 21:32:50 -06:00
|
|
|
template<typename Locks>
|
|
|
|
inline bool any_lock(Locks locks) {
|
2022-08-28 19:08:40 -06:00
|
|
|
return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); });
|
|
|
|
}
|
|
|
|
|
2022-09-02 21:32:50 -06:00
|
|
|
template<typename Locks, typename LockT>
|
|
|
|
inline bool only_lock(Locks locks, LockT* this_lock) {
|
2022-08-28 19:08:40 -06:00
|
|
|
return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) {
|
2022-09-02 21:32:50 -06:00
|
|
|
if (lock == this_lock) {
|
2022-08-28 19:08:40 -06:00
|
|
|
return lock->isLocked();
|
|
|
|
}
|
|
|
|
return !lock->isLocked();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-09-02 21:32:50 -06:00
|
|
|
bool AnnouncementLock::isAnyLocked() const {
|
|
|
|
return any_lock(locks);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AnnouncementLock::isOnlyLocked() const {
|
|
|
|
return only_lock(locks, this);
|
|
|
|
}
|
|
|
|
|
2022-08-28 19:08:40 -06:00
|
|
|
bool PlayerLock::isAnyLocked() const {
|
2022-09-02 21:32:50 -06:00
|
|
|
return any_lock(locks);
|
2022-08-28 19:08:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
bool PlayerLock::isOnlyLocked() const {
|
2022-09-02 21:32:50 -06:00
|
|
|
return only_lock(locks, this);
|
2022-08-28 19:08:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace pausing {
|
|
|
|
AnnouncementLock announcementLock("monitor");
|
|
|
|
PlayerLock playerLock("monitor");
|
|
|
|
|
|
|
|
const size_t array_size = sizeof(decltype(df::announcements::flags)) / sizeof(df::announcement_flags);
|
|
|
|
bool state_saved = false; // indicates whether a restore state is ok
|
|
|
|
bool announcements_disabled = false; // indicates whether disable or restore was last enacted, could use a better name
|
|
|
|
bool saved_states[array_size]; // state to restore
|
|
|
|
bool locked_states[array_size]; // locked state (re-applied each frame)
|
|
|
|
bool allow_player_pause = true; // toggles player pause ability
|
|
|
|
|
|
|
|
using df::global::ui;
|
|
|
|
using namespace df::enums;
|
|
|
|
struct player_pause_hook : df::viewscreen_dwarfmodest {
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key>* input)) {
|
|
|
|
if ((ui->main.mode == ui_sidebar_mode::Default) && !allow_player_pause) {
|
|
|
|
input->erase(interface_key::D_PAUSE);
|
|
|
|
}
|
|
|
|
INTERPOSE_NEXT(feed)(input);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(player_pause_hook, feed);
|
|
|
|
}
|
|
|
|
using namespace pausing;
|
|
|
|
|
|
|
|
AnnouncementLock* World::AcquireAnnouncementPauseLock(const char* name) {
|
|
|
|
return new AnnouncementLock(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
PlayerLock* World::AcquirePlayerPauseLock(const char* name) {
|
|
|
|
return new PlayerLock(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
void World::ReleasePauseLock(Lock* lock){
|
|
|
|
delete lock;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::DisableAnnouncementPausing() {
|
|
|
|
if (!announcementLock.isAnyLocked()) {
|
|
|
|
for (auto& flag : df::global::d_init->announcements.flags) {
|
|
|
|
flag.bits.PAUSE = false;
|
|
|
|
}
|
|
|
|
announcements_disabled = true;
|
|
|
|
}
|
|
|
|
return announcements_disabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::SaveAnnouncementSettings() {
|
|
|
|
if (!announcementLock.isAnyLocked()) {
|
|
|
|
for (size_t i = 0; i < array_size; ++i) {
|
|
|
|
saved_states[i] = df::global::d_init->announcements.flags[i].bits.PAUSE;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::RestoreAnnouncementSettings() {
|
|
|
|
if (!announcementLock.isAnyLocked() && state_saved) {
|
|
|
|
for (size_t i = 0; i < array_size; ++i) {
|
|
|
|
df::global::d_init->announcements.flags[i].bits.PAUSE = saved_states[i];
|
|
|
|
}
|
|
|
|
announcements_disabled = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::EnablePlayerPausing() {
|
|
|
|
if (!playerLock.isAnyLocked()) {
|
|
|
|
allow_player_pause = true;
|
|
|
|
}
|
|
|
|
return allow_player_pause;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::DisablePlayerPausing() {
|
|
|
|
if (!playerLock.isAnyLocked()) {
|
|
|
|
allow_player_pause = false;
|
|
|
|
}
|
|
|
|
return !allow_player_pause;
|
|
|
|
}
|
|
|
|
|
|
|
|
void World::Update() {
|
|
|
|
static bool did_once = false;
|
|
|
|
if (!did_once) {
|
|
|
|
did_once = true;
|
|
|
|
INTERPOSE_HOOK(player_pause_hook, feed).apply();
|
|
|
|
}
|
|
|
|
if (announcementLock.isAnyLocked()) {
|
|
|
|
for (size_t i = 0; i < array_size; ++i) {
|
|
|
|
df::global::d_init->announcements.flags[i].bits.PAUSE = locked_states[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-05-12 07:27:51 -06:00
|
|
|
uint32_t World::ReadCurrentYear()
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
return DF_GLOBAL_VALUE(cur_year, 0);
|
2010-05-12 07:27:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t World::ReadCurrentTick()
|
|
|
|
{
|
2016-01-09 17:28:12 -07:00
|
|
|
// prevent this from returning anything less than 0,
|
|
|
|
// to avoid day/month calculations with 0xffffffff
|
|
|
|
return std::max(0, DF_GLOBAL_VALUE(cur_year_tick, 0));
|
2010-05-12 07:27:51 -06:00
|
|
|
}
|
|
|
|
|
2011-03-01 14:18:26 -07:00
|
|
|
bool World::ReadGameMode(t_gamemodes& rd)
|
2011-02-28 22:59:23 -07:00
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
if(df::global::gamemode && df::global::gametype)
|
2011-03-01 14:18:26 -07:00
|
|
|
{
|
2012-06-16 04:42:56 -06:00
|
|
|
rd.g_mode = (DFHack::GameMode)*df::global::gamemode;
|
|
|
|
rd.g_type = (DFHack::GameType)*df::global::gametype;
|
2011-03-01 14:18:26 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
bool World::WriteGameMode(const t_gamemodes & wr)
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
if(df::global::gamemode && df::global::gametype)
|
2011-03-01 14:18:26 -07:00
|
|
|
{
|
2012-06-16 04:42:56 -06:00
|
|
|
*df::global::gamemode = wr.g_mode;
|
|
|
|
*df::global::gametype = wr.g_type;
|
2011-03-01 14:18:26 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2011-02-28 22:59:23 -07:00
|
|
|
}
|
|
|
|
|
2011-03-18 04:09:26 -06:00
|
|
|
/*
|
|
|
|
FIXME: Japa said that he had to do this with the time stuff he got from here
|
|
|
|
Investigate.
|
|
|
|
|
|
|
|
currentYear = Wold->ReadCurrentYear();
|
|
|
|
currentTick = Wold->ReadCurrentTick();
|
|
|
|
currentMonth = (currentTick+9)/33600;
|
|
|
|
currentDay = ((currentTick+9)%33600)/1200;
|
|
|
|
currentHour = ((currentTick+9)-(((currentMonth*28)+currentDay)*1200))/50;
|
|
|
|
currentTickRel = (currentTick+9)-(((((currentMonth*28)+currentDay)*24)+currentHour)*50);
|
|
|
|
*/
|
|
|
|
|
2010-08-20 06:10:05 -06:00
|
|
|
// FIX'D according to this:
|
2010-06-05 16:56:09 -06:00
|
|
|
/*
|
|
|
|
World::ReadCurrentMonth and World::ReadCurrentDay
|
|
|
|
« Sent to: peterix on: June 04, 2010, 04:44:30 »
|
|
|
|
« You have forwarded or responded to this message. »
|
2011-03-19 23:20:23 -06:00
|
|
|
|
2010-06-05 16:56:09 -06:00
|
|
|
Shouldn't these be /28 and %28 instead of 24? There're 28 days in a DF month.
|
|
|
|
Using 28 and doing the calculation on the value stored at the memory location
|
|
|
|
specified by memory.xml gets me the current month/date.
|
|
|
|
*/
|
2010-05-12 07:27:51 -06:00
|
|
|
uint32_t World::ReadCurrentMonth()
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
return ReadCurrentTick() / 1200 / 28;
|
2010-05-12 07:27:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t World::ReadCurrentDay()
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
return ((ReadCurrentTick() / 1200) % 28) + 1;
|
2010-05-12 07:27:51 -06:00
|
|
|
}
|
2010-09-01 09:22:19 -06:00
|
|
|
|
|
|
|
uint8_t World::ReadCurrentWeather()
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
if (df::global::current_weather)
|
2012-05-23 11:51:03 -06:00
|
|
|
return (*df::global::current_weather)[2][2];
|
2010-09-01 09:22:19 -06:00
|
|
|
return 0;
|
|
|
|
}
|
2011-03-18 04:09:26 -06:00
|
|
|
|
2010-09-16 07:09:42 -06:00
|
|
|
void World::SetCurrentWeather(uint8_t weather)
|
|
|
|
{
|
2012-10-06 03:46:20 -06:00
|
|
|
if (df::global::current_weather)
|
2012-05-23 11:51:03 -06:00
|
|
|
memset(df::global::current_weather, weather, 25);
|
2010-09-16 07:09:42 -06:00
|
|
|
}
|
2011-07-16 17:00:50 -06:00
|
|
|
|
|
|
|
string World::ReadWorldFolder()
|
|
|
|
{
|
2016-08-14 10:41:09 -06:00
|
|
|
return world->cur_savegame.save_dir;
|
2011-07-16 17:00:50 -06:00
|
|
|
}
|
2012-01-07 08:21:31 -07:00
|
|
|
|
2015-03-09 18:08:21 -06:00
|
|
|
bool World::isFortressMode(df::game_type t)
|
|
|
|
{
|
|
|
|
if (t == -1 && df::global::gametype)
|
|
|
|
t = *df::global::gametype;
|
|
|
|
return (t == game_type::DWARF_MAIN || t == game_type::DWARF_RECLAIM ||
|
|
|
|
t == game_type::DWARF_UNRETIRE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isAdventureMode(df::game_type t)
|
|
|
|
{
|
|
|
|
if (t == -1 && df::global::gametype)
|
|
|
|
t = *df::global::gametype;
|
|
|
|
return (t == game_type::ADVENTURE_MAIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isArena(df::game_type t)
|
|
|
|
{
|
|
|
|
if (t == -1 && df::global::gametype)
|
|
|
|
t = *df::global::gametype;
|
|
|
|
return (t == game_type::DWARF_ARENA || t == game_type::ADVENTURE_ARENA);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isLegends(df::game_type t)
|
|
|
|
{
|
|
|
|
if (t == -1 && df::global::gametype)
|
|
|
|
t = *df::global::gametype;
|
|
|
|
return (t == game_type::VIEW_LEGENDS);
|
|
|
|
}
|
|
|
|
|
2012-04-01 06:43:40 -06:00
|
|
|
PersistentDataItem World::AddPersistentData(const std::string &key)
|
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
return Persistence::addItem(key);
|
2012-01-07 08:21:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
PersistentDataItem World::GetPersistentData(const std::string &key)
|
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
return Persistence::getByKey(key);
|
2012-04-01 06:43:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
PersistentDataItem World::GetPersistentData(int entry_id)
|
|
|
|
{
|
|
|
|
if (entry_id < 100)
|
|
|
|
return PersistentDataItem();
|
|
|
|
|
2018-08-26 17:26:09 -06:00
|
|
|
return Persistence::getByIndex(size_t(entry_id - 100));
|
2012-01-07 08:21:31 -07:00
|
|
|
}
|
|
|
|
|
2012-04-05 01:32:23 -06:00
|
|
|
PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
|
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
bool temp = false;
|
|
|
|
if (!added)
|
|
|
|
added = &temp;
|
2012-04-05 01:32:23 -06:00
|
|
|
|
2018-08-26 17:26:09 -06:00
|
|
|
return Persistence::getByKey(key, added);
|
2012-04-05 01:32:23 -06:00
|
|
|
}
|
|
|
|
|
2012-04-01 06:43:40 -06:00
|
|
|
void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix)
|
2012-01-07 08:21:31 -07:00
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
if (prefix && key.empty())
|
2012-04-01 06:43:40 -06:00
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
Persistence::getAll(*vec);
|
|
|
|
}
|
|
|
|
else if (prefix)
|
|
|
|
{
|
|
|
|
std::string min = key;
|
|
|
|
if (min.back() != '/')
|
2012-04-01 06:43:40 -06:00
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
min.push_back('/');
|
2012-04-01 06:43:40 -06:00
|
|
|
}
|
2018-08-26 17:26:09 -06:00
|
|
|
std::string max = min;
|
|
|
|
++max.back();
|
2012-04-01 06:43:40 -06:00
|
|
|
|
2018-08-26 17:26:09 -06:00
|
|
|
Persistence::getAllByKeyRange(*vec, min, max);
|
2012-04-01 06:43:40 -06:00
|
|
|
}
|
2018-08-26 17:26:09 -06:00
|
|
|
else
|
2012-04-01 06:43:40 -06:00
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
Persistence::getAllByKey(*vec, key);
|
2012-01-07 08:21:31 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-01 06:43:40 -06:00
|
|
|
bool World::DeletePersistentData(const PersistentDataItem &item)
|
2012-01-07 08:21:31 -07:00
|
|
|
{
|
2018-08-26 17:26:09 -06:00
|
|
|
return Persistence::deleteItem(item);
|
2012-01-07 08:21:31 -07:00
|
|
|
}
|
2012-10-11 07:34:34 -06:00
|
|
|
|
|
|
|
df::tile_bitmask *World::getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create)
|
|
|
|
{
|
|
|
|
if (!block)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
int id = item.raw_id();
|
|
|
|
if (id > -100)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < block->block_events.size(); i++)
|
|
|
|
{
|
|
|
|
auto ev = block->block_events[i];
|
|
|
|
if (ev->getType() != block_square_event_type::world_construction)
|
|
|
|
continue;
|
|
|
|
auto wcsev = strict_virtual_cast<df::block_square_event_world_constructionst>(ev);
|
|
|
|
if (!wcsev || wcsev->construction_id != id)
|
|
|
|
continue;
|
|
|
|
return &wcsev->tile_bitmask;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!create)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
auto ev = df::allocate<df::block_square_event_world_constructionst>();
|
|
|
|
if (!ev)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ev->construction_id = id;
|
|
|
|
ev->tile_bitmask.clear();
|
|
|
|
vector_insert_at(block->block_events, 0, (df::block_square_event*)ev);
|
|
|
|
|
|
|
|
return &ev->tile_bitmask;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block)
|
|
|
|
{
|
|
|
|
if (!block)
|
|
|
|
return false;
|
|
|
|
int id = item.raw_id();
|
|
|
|
if (id > -100)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool found = false;
|
|
|
|
for (int i = block->block_events.size()-1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
auto ev = block->block_events[i];
|
|
|
|
if (ev->getType() != block_square_event_type::world_construction)
|
|
|
|
continue;
|
|
|
|
auto wcsev = strict_virtual_cast<df::block_square_event_world_constructionst>(ev);
|
|
|
|
if (!wcsev || wcsev->construction_id != id)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
delete wcsev;
|
|
|
|
vector_erase_at(block->block_events, i);
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|