diff --git a/includes/ajaxHandler/admin.class.php b/includes/ajaxHandler/admin.class.php index 8d816d3e..6e65efeb 100644 --- a/includes/ajaxHandler/admin.class.php +++ b/includes/ajaxHandler/admin.class.php @@ -459,7 +459,7 @@ class AjaxAdmin extends AjaxHandler DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $id, time(), User::$id, $status); if ($msg) - DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)' , $id, time(), User::$id, $msg); + DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)', $id, time(), User::$id, $msg); return true; }; diff --git a/includes/ajaxHandler/arenateam.class.php b/includes/ajaxHandler/arenateam.class.php index df95cafd..715924bc 100644 --- a/includes/ajaxHandler/arenateam.class.php +++ b/includes/ajaxHandler/arenateam.class.php @@ -1,7 +1,7 @@ inputFields as $inp => [$type, $valid, $asArray]) { if (!isset($_POST[$inp]) || $_POST[$inp] === '') diff --git a/includes/conditions.class.php b/includes/conditions.class.php index 9f3b4c7c..d3c10e99 100644 --- a/includes/conditions.class.php +++ b/includes/conditions.class.php @@ -222,11 +222,11 @@ class Conditions public function getBySourceEntry(int $entry, int ...$srcType) : bool { $this->rows = DB::World()->select( - 'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`, - `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition` - FROM conditions - WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceEntry` = ?d - ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC', + 'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`, + `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition` + FROM conditions + WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceEntry` = ?d + ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC', $srcType, $entry ); @@ -236,11 +236,11 @@ class Conditions public function getBySourceGroup(int $group, int ...$srcType) : bool { $this->rows = DB::World()->select( - 'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`, - `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition` - FROM conditions - WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceGroup` = ?d - ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC', + 'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`, + `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition` + FROM conditions + WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceGroup` = ?d + ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC', $srcType, $group ); @@ -258,13 +258,14 @@ class Conditions return false; $this->rows = DB::World()->select(sprintf( - 'SELECT c1.`SourceTypeOrReferenceId`, c1.`SourceEntry`, c1.`SourceGroup`, c1.`SourceId`, c1.`ElseGroup`, - c1.`ConditionTypeOrReference`, c1.`ConditionTarget`, c1.`ConditionValue1`, c1.`ConditionValue2`, c1.`ConditionValue3`, c1.`NegativeCondition` - FROM conditions c1 - JOIN conditions c2 ON c1.SourceTypeOrReferenceId = c2.SourceTypeOrReferenceId AND c1.SourceEntry = c2.SourceEntry AND c1.SourceGroup = c2.SourceGroup AND c1.SourceId = c2.SourceId - WHERE %s - ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC' - , implode(' OR ', $lookups))); + 'SELECT c1.`SourceTypeOrReferenceId`, c1.`SourceEntry`, c1.`SourceGroup`, c1.`SourceId`, c1.`ElseGroup`, + c1.`ConditionTypeOrReference`, c1.`ConditionTarget`, c1.`ConditionValue1`, c1.`ConditionValue2`, c1.`ConditionValue3`, c1.`NegativeCondition` + FROM conditions c1 + JOIN conditions c2 ON c1.SourceTypeOrReferenceId = c2.SourceTypeOrReferenceId AND c1.SourceEntry = c2.SourceEntry AND c1.SourceGroup = c2.SourceGroup AND c1.SourceId = c2.SourceId + WHERE %s + ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC', + implode(' OR ', $lookups)) + ); return $this->fromSource(); } diff --git a/includes/defines.php b/includes/defines.php index 6db2a499..5f1b4c58 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -149,15 +149,19 @@ define('U_GROUP_COMMENTS_MODERATOR', (U_GROUP_MODERATOR|U_GROUP_LOCALIZER define('U_GROUP_PREMIUM_PERMISSIONS', (U_GROUP_PREMIUM|U_GROUP_STAFF|U_GROUP_VIP)); // Locales -define('LOCALE_EN', 0); -define('LOCALE_KR', 1); // unused -define('LOCALE_FR', 2); -define('LOCALE_DE', 3); -define('LOCALE_CN', 4); -define('LOCALE_TW', 5); // unused -define('LOCALE_ES', 6); -define('LOCALE_MX', 7); // unused -define('LOCALE_RU', 8); +define('LOCALE_EN', 0); // enGB, enUS +define('LOCALE_KR', 1); // koKR [aowwo unused] +define('LOCALE_FR', 2); // frFR +define('LOCALE_DE', 3); // deDE +define('LOCALE_CN', 4); // zhCN, enCN +define('LOCALE_TW', 5); // zhTW, enTW [aowow unused] +define('LOCALE_ES', 6); // esES +define('LOCALE_MX', 7); // esMX [aowow unused] +define('LOCALE_RU', 8); // ruRU +define('LOCALE_JP', 9); // jaJP [aowow & 335 unused] +define('LOCALE_PT', 10); // ptPT, ptBR [aowow unused] +define('LOCALE_IT', 11); // itIT [aowow & 335 unused] +define('LOCALE_MASK_ALL', 0b000101011101); // red buttons on the top of the page define('BUTTON_WOWHEAD', 0); diff --git a/includes/game.php b/includes/game.php index 0e3c94b6..83c38cdd 100644 --- a/includes/game.php +++ b/includes/game.php @@ -263,7 +263,7 @@ class Game } if (empty(self::$alphaMapCache[$areaId])) - self::$alphaMapCache[$areaId] = imagecreatefrompng($file); + self::$alphaMapCache[$areaId] = imagecreatefrompng($file); // alphaMaps are 1000 x 1000, adapt points [black => valid point] if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10)) @@ -318,16 +318,16 @@ class Game switch ($type) { case Type::NPC: - $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids); + $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids); break; case Type::OBJECT: - $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids); + $result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids); break; case Type::SOUND: - $result = DB::AoWoW()->select('SELECT `soundId` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ?_soundemitters WHERE `soundId` IN (?a)', $guids); + $result = DB::AoWoW()->select('SELECT `soundId` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ?_soundemitters WHERE `soundId` IN (?a)', $guids); break; case Type::AREATRIGGER: - $result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM ?_areatrigger WHERE `id` IN (?a)', $guids); + $result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM ?_areatrigger WHERE `id` IN (?a)', $guids); break; default: trigger_error('Game::getWorldPosForGUID - instanced with unsupported TYPE #'.$type, E_USER_WARNING); diff --git a/includes/profiler.class.php b/includes/profiler.class.php index 4dfc4b02..43077f0b 100644 --- a/includes/profiler.class.php +++ b/includes/profiler.class.php @@ -6,8 +6,17 @@ if (!defined('AOWOW_REVISION')) class Profiler { - const PID_FILE = 'config/pr-queue-pid'; - const CHAR_GMFLAGS = 0x1 | 0x8 | 0x10 | 0x20; // PLAYER_EXTRA_ :: GM_ON | TAXICHEAT | GM_INVISIBLE | GM_CHAT + public const PID_FILE = 'config/pr-queue-pid'; + public const CHAR_GMFLAGS = 0x1 | 0x8 | 0x10 | 0x20; // PLAYER_EXTRA_ :: GM_ON | TAXICHEAT | GM_INVISIBLE | GM_CHAT + + public const REGIONS = array( // see cfg_categories.dbc + 'us' => [2, 3, 4, 5], // US (us, oceanic, latin america, americas - tournament) + 'kr' => [6, 7], // KR (kr, tournament) + 'eu' => [8, 9, 10, 11, 12, 13], // EU (english, german, french, spanish, russian, eu - tournament) + 'tw' => [14, 15], // TW (tw, tournament) + 'cn' => [16, 17, 18, 19, 20, 21, 22, 23, 24, 25], // CN (cn, CN1-8, tournament) + 'dev' => [1, 26, 27, 28, 30] // Development, Test Server, Test Server - tournament, QA Server, Test Server 2 + ); private static $realms = []; @@ -190,7 +199,7 @@ class Profiler WHEN `timezone` BETWEEN 8 AND 13 THEN "eu" # GB, DE, FR, ES, RU, EU-Tournament WHEN `timezone` BETWEEN 14 AND 15 THEN "tw" # TW, TW-Tournament WHEN `timezone` BETWEEN 16 AND 25 THEN "cn" # CN, CN1-8, CN-Tournament - ELSE "dev" END AS "region", # 1: Dev, 26: Test, 28: QA, 30: Test2, 31+: misc + ELSE "dev" END AS "region", # 1: Dev, 26: Test, 27: Test Tournament, 28: QA, 30: Test2, 31+: misc `allowedSecurityLevel` AS "access" FROM `realmlist` WHERE `gamebuild` = ?d', diff --git a/includes/types/enchantment.class.php b/includes/types/enchantment.class.php index 8cddd361..6016ed6b 100644 --- a/includes/types/enchantment.class.php +++ b/includes/types/enchantment.class.php @@ -183,7 +183,7 @@ class EnchantmentListFilter extends Filter 38 => [FILTER_CR_NUMERIC, 'is.rgdatkpwr', NUM_CAST_INT, true], // rgdatkpwr 39 => [FILTER_CR_NUMERIC, 'is.rgdhitrtng', NUM_CAST_INT, true], // rgdhitrtng 40 => [FILTER_CR_NUMERIC, 'is.rgdcritstrkrtng', NUM_CAST_INT, true], // rgdcritstrkrtng - 41 => [FILTER_CR_NUMERIC, 'is.armor' , NUM_CAST_INT, true], // armor + 41 => [FILTER_CR_NUMERIC, 'is.armor', NUM_CAST_INT, true], // armor 42 => [FILTER_CR_NUMERIC, 'is.defrtng', NUM_CAST_INT, true], // defrtng 43 => [FILTER_CR_NUMERIC, 'is.block', NUM_CAST_INT, true], // block 44 => [FILTER_CR_NUMERIC, 'is.blockrtng', NUM_CAST_INT, true], // blockrtng diff --git a/includes/types/profile.class.php b/includes/types/profile.class.php index 2ef83cb6..165d2dd8 100644 --- a/includes/types/profile.class.php +++ b/includes/types/profile.class.php @@ -659,8 +659,11 @@ class RemoteProfileList extends ProfileList $baseData = $guildData = []; foreach ($this->iterate() as $guid => $__) { + $realmId = $this->getField('realm'); + $guildGUID = $this->getField('guild'); + $baseData[$guid] = array( - 'realm' => $this->getField('realm'), + 'realm' => $realmId, 'realmGUID' => $this->getField('guid'), 'name' => $this->getField('name'), 'renameItr' => $this->getField('renameItr'), @@ -668,15 +671,15 @@ class RemoteProfileList extends ProfileList 'class' => $this->getField('class'), 'level' => $this->getField('level'), 'gender' => $this->getField('gender'), - 'guild' => $this->getField('guild') ?: null, - 'guildrank' => $this->getField('guild') ? $this->getField('guildrank') : null, + 'guild' => $guildGUID ?: null, + 'guildrank' => $guildGUID ? $this->getField('guildrank') : null, 'cuFlags' => PROFILER_CU_NEEDS_RESYNC ); - if ($this->getField('guild')) - $guildData[] = array( - 'realm' => $this->getField('realm'), - 'realmGUID' => $this->getField('guild'), + if ($guildGUID && empty($guildData[$realmId.'-'.$guildGUID])) + $guildData[$realmId.'-'.$guildGUID] = array( + 'realm' => $realmId, + 'realmGUID' => $guildGUID, 'name' => $this->getField('guildname'), 'nameUrl' => Profiler::urlize($this->getField('guildname')), 'cuFlags' => PROFILER_CU_NEEDS_RESYNC diff --git a/includes/types/quest.class.php b/includes/types/quest.class.php index 1e193608..b0c9d39b 100644 --- a/includes/types/quest.class.php +++ b/includes/types/quest.class.php @@ -654,9 +654,9 @@ class QuestListFilter extends Filter return false; if ($cr[1]) - return ['AND', ['zoneOrSort', 0, '>'], [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE , '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY , '&'], 0]]; + return ['AND', ['zoneOrSort', 0, '>'], [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE, '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY, '&'], 0]]; else - return ['OR', ['zoneOrSort', 0, '<'], ['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE , '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY , '&']]; + return ['OR', ['zoneOrSort', 0, '<'], ['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE, '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY, '&']]; } protected function cbSpellRewards($cr) diff --git a/includes/types/spell.class.php b/includes/types/spell.class.php index 72745cfc..e8e751d6 100644 --- a/includes/types/spell.class.php +++ b/includes/types/spell.class.php @@ -2083,7 +2083,6 @@ class SpellList extends BaseType if ($mask = $this->curTpl['reqRaceMask']) $data[$this->id]['reqrace'] = $mask; - if ($addInfoMask & ITEMINFO_MODEL) { // may have multiple models set, in this case i've no idea what should be picked @@ -2132,10 +2131,8 @@ class SpellList extends BaseType if ($addMask & GLOBALINFO_SELF) { - $iconString = $this->curTpl['iconStringAlt'] ? 'iconStringAlt' : 'iconString'; - $data[Type::SPELL][$id] = array( - 'icon' => $this->curTpl[$iconString], + 'icon' => $this->curTpl['iconStringAlt'] ?: $this->curTpl['iconString'], 'name' => $this->getField('name', true), ); } @@ -2255,7 +2252,7 @@ class SpellList extends BaseType 'ti' => $this->id, 's' => empty($this->curTpl['skillLines']) ? 0 : $this->curTpl['skillLines'][0], 'c' => $this->curTpl['typeCat'], - 'icon' => $this->curTpl['iconStringAlt'] ? $this->curTpl['iconStringAlt'] : $this->curTpl['iconString'], + 'icon' => $this->curTpl['iconStringAlt'] ?: $this->curTpl['iconString'], ); } diff --git a/includes/user.class.php b/includes/user.class.php index 1909e0e6..5686a144 100644 --- a/includes/user.class.php +++ b/includes/user.class.php @@ -592,6 +592,9 @@ class User $gUser['excludegroups'] = self::$excludeGroups; $gUser['settings'] = (new StdClass); // profiler requires this to be set; has property premiumborder (NYI) + if (Cfg::get('DEBUG') && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_TESTER)) + $gUser['debug'] = true; // csv id-list output option on listviews + if ($_ = self::getProfilerExclusions()) $gUser = array_merge($gUser, $_); diff --git a/includes/utilities.php b/includes/utilities.php index 3e4e5a95..1d7edbeb 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -415,7 +415,7 @@ abstract class CLI $keyId = $ordinals[$idx]; // skip char if horizontal tab or \r if followed by \n - if ($keyId == self::CHR_TAB || ($keyId == self::CHR_CR && ($ordinals[$i + 1] ?? '') == self::CHR_LF)) + if ($keyId == self::CHR_TAB || ($keyId == self::CHR_CR && ($ordinals[$idx + 1] ?? '') == self::CHR_LF)) continue; if ($keyId == self::CHR_BACKSPACE) @@ -1336,8 +1336,11 @@ abstract class Util /* Good Skill */ /**************/ - public static function getEquipmentScore($itemLevel, $quality, $slot, $nSockets = 0) + public static function getEquipmentScore(int $itemLevel, int $quality, int $slot, int $nSockets = 0) : float { + if ($itemLevel < 0) // can this even happen? + $itemLevel = 0; + $score = $itemLevel; // quality mod @@ -1416,10 +1419,10 @@ abstract class Util $score -= $nSockets * self::GEM_SCORE_BASE_BC; } - return round(max(0.0, $score), 4); + return round($score, 4); } - public static function getGemScore($itemLevel, $quality, $profSpec = false, $itemId = 0) + public static function getGemScore(int $itemLevel, int $quality, bool $profSpec = false, int $itemId = 0) : float { // prepare score-lookup if (empty(self::$perfectGems)) @@ -1463,8 +1466,11 @@ abstract class Util return 0.0; } - public static function getEnchantmentScore($itemLevel, $quality, $profSpec = false, $idOverride = 0) + public static function getEnchantmentScore(int $itemLevel, int $quality, bool $profSpec = false, int $idOverride = 0) : float { + if ($itemLevel < 0) // can this even happen? + $itemLevel = 0; + // some hardcoded values, that defy lookups (cheaper but not skillbound profession versions of spell threads, leg armor) if (in_array($idOverride, [3327, 3328, 3872, 3873])) return 20.0; @@ -1473,25 +1479,25 @@ abstract class Util return 40.0; // other than the constraints (0 - 20 points; 40 for profession perks), everything in here is guesswork - $score = max(min($itemLevel, 80), 0); + $score = min($itemLevel, 80); switch ($quality) { case ITEM_QUALITY_HEIRLOOM: // because i say so! - $score = 80.0; + $score = 20.0; break; case ITEM_QUALITY_RARE: - $score /= 1.2; + $score /= 4.8; break; case ITEM_QUALITY_UNCOMMON: - $score /= 1.6; + $score /= 6.4; break; case ITEM_QUALITY_NORMAL: - $score /= 2.5; + $score /= 10.0; break; } - return round(max(0.0, $score / 4), 4); + return round($score, 4); } public static function fixWeaponScores($class, $talents, $mainHand, $offHand) @@ -1595,6 +1601,7 @@ abstract class Util } } + abstract class Type { public const NPC = 1; diff --git a/localization/lang.class.php b/localization/lang.class.php index 81cb9be5..8fa9cd50 100644 --- a/localization/lang.class.php +++ b/localization/lang.class.php @@ -1,5 +1,8 @@ |r $var = preg_replace_callback('/\|c([[:xdigit:]]{2})([[:xdigit:]]{6})(.+?)\|r/is', function ($m) use ($fmt) diff --git a/pages/admin.php b/pages/admin.php index 1638e1cd..b3f74604 100644 --- a/pages/admin.php +++ b/pages/admin.php @@ -213,7 +213,7 @@ class AdminPage extends GenericPage $ssData = []; $nMatches = 0; - if ($this->_get['type'] && $this->_get['typeId']) + if ($this->_get['type'] && $this->_get['typeid']) { $ssData = CommunityContent::getScreenshotsForManager($this->_get['type'], $this->_get['typeid']); $nMatches = count($ssData); diff --git a/pages/item.php b/pages/item.php index 0b0f2ec3..de7c4848 100644 --- a/pages/item.php +++ b/pages/item.php @@ -430,8 +430,8 @@ class ItemPage extends genericPage if ($prev['jsonequip'] == $cur['jsonequip'] && $prev['name'] == $cur['name']) { $prev['chance'] += $cur['chance']; - array_splice($this->subItems['data'], $i , 1); - array_splice($this->subItems['randIds'], $i , 1); + array_splice($this->subItems['data'], $i, 1); + array_splice($this->subItems['randIds'], $i, 1); $i = 1; } } diff --git a/setup/tools/filegen/gems.func.php b/setup/tools/filegen/gems.func.php index 7eb15f21..b3781b51 100644 --- a/setup/tools/filegen/gems.func.php +++ b/setup/tools/filegen/gems.func.php @@ -31,7 +31,7 @@ if (!CLI) $gems = DB::Aowow()->Select( 'SELECT i.id AS itemId, i.name_loc0, i.name_loc2, i.name_loc3, i.name_loc4, i.name_loc6, i.name_loc8, - IF (i.id < 36000 OR i.itemLevel < 70, 1 , 2) AS expansion, + IF (i.id < 36000 OR i.itemLevel < 70, ?d, ?d) AS expansion, i.quality, ic.name AS icon, i.gemEnchantmentId AS enchId, @@ -41,7 +41,9 @@ if (!CLI) FROM ?_items i JOIN ?_icons ic ON ic.id = i.iconId WHERE i.gemEnchantmentId <> 0 - ORDER BY i.id DESC'); + ORDER BY i.id DESC', + EXP_BC, EXP_WOTLK + ); $success = true; // check directory-structure