turns-00053.parquet:42678
2f9f8df0b4ed55f8ae2f21d7degenerate_repetitionAbsentFinal dense release
Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.
2f9f8df0b4ed55f8ae2f21d7qual sua versão? GPT-4o Mini
Eu sou baseado na arquitetura GPT-4, mas não tenho uma designação específica como "GPT-4o Mini". Estou aqui para ajudar com informações e responder a perguntas da melhor forma possível! Se você tiver alguma dúvida ou precisar de assistência, fique à vontade para perguntar.
d4a1f953d3a3f85330ecbc5aquais são os models? e qual o seu?
Atualmente, a OpenAI lançou várias versões de modelos de linguagem, incluindo: 1. **GPT-1**: O primeiro modelo da série, que estabeleceu as bases para os modelos futuros. 2. **GPT-2**: Uma versão maior e mais potente que chamou atenção por sua capacidade de gerar texto coerente e relevante. 3. **GPT-3**: Um modelo muito maior que o anterior, com 175 bilhões de parâmetros, e amplamente utilizado em diversas aplicações. 4. **GPT-3.5**: Uma versão intermediária que trouxe melhorias na compreensão e na geração de texto. 5. **GPT-4**: Uma versão ainda mais avançada, com aprimoramentos significativos em termos de compreensão, coerência e geração de texto. Eu sou baseado na arquitetura GPT-4. Se você tiver mais perguntas sobre os modelos ou suas capacidades, estou aqui para ajudar!
00bd07e28b0cf0582a3ba39dusing System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Rust;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Oxide.Plugins
{
[Info("Dungeons", "Marte6", "1.0.0")]
public class Dungeons : RustPlugin
{
private ConfigData _configData;
private DynamicConfigFile _dataFile;
private DungeonData _dungeonData;
private Timer _autoSpawnTimer;
private Timer _dungeonCheckTimer;
private Timer _updateMarkersTimer;
private Dictionary<DungeonTierConfig, string> _tierNameMap;
private const ulong OwnerID = 1020304050;
#region Hooks
private void OnServerInitialized()
{
InitializeTierNameMap();
LoadData();
RemoveAllDungeons();
StartTimerAutoSpawnDungeons();
StartTimerRemoveInactiveDungeons();
StartTimerUpdateMarkers();
SaveData();
}
private void OnServerSave() => SaveData();
private void Unload()
{
RemoveAllDungeons();
_autoSpawnTimer?.Destroy();
_dungeonCheckTimer?.Destroy();
_updateMarkersTimer?.Destroy();
SaveData();
}
object OnTurretTarget(AutoTurret turret, BaseCombatEntity target)
{
if (IsDungeonEntity(target))
{
return false;
}
return null;
}
object OnEntityTakeDamage(ScientistNPC scientistNPC, HitInfo info)
{
if (scientistNPC == null || info == null || info.Initiator == null)
return null;
AutoTurret autoTurret = info.Initiator as AutoTurret;
if (autoTurret == null)
return null;
if (IsDungeonEntity(autoTurret))
return true;
return null;
}
object CanEntityTakeDamage(AutoTurret autoTurret, HitInfo hitinfo)
{
if (autoTurret == null || hitinfo == null)
return null;
if (IsDungeonEntity(autoTurret))
{
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
else
return true;
}
return null;
}
object CanEntityTakeDamage(StorageContainer storageContainer, HitInfo hitinfo)
{
if (storageContainer == null || hitinfo == null)
return null;
if (IsDungeonEntity(storageContainer))
{
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
else
return true;
}
return null;
}
#endregion
#region Chat Commands
[ChatCommand("dun")]
private void CreateDungeonCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
AutoSpawnDungeon(player);
}
[ChatCommand("RemoveInactiveDungeons")]
private void RemoveInactiveDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
var removedDungeons = RemoveInactiveDungeons();
NotifyPlayerOnRemoval(player, removedDungeons);
}
[ChatCommand("RemoveAllDungeons")]
private void RemoveAllDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
var removedDungeons = RemoveAllDungeons();
NotifyPlayerOnRemoval(player, removedDungeons);
}
[ChatCommand("ForceRemoveAllDungeons")]
private void ForceRemoveAllDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
CleanupExistingEntities();
player.ChatMessage("All entities related to this plugin have been cleaned up.");
}
#endregion
#region Dungeon Management
private void StartTimerAutoSpawnDungeons()
{
if (_configData.DungeonSpawn.EnableAutoSpawn)
{
_autoSpawnTimer = timer.Every(
_configData.DungeonSpawn.AutoSpawnCycleInterval,
() =>
{
AutoSpawnDungeon();
}
);
}
}
private void StartTimerRemoveInactiveDungeons()
{
_dungeonCheckTimer = timer.Every(
60f,
() =>
{
var removedDungeons = RemoveInactiveDungeons();
if (removedDungeons.Count > 0)
{
foreach (var dungeon in removedDungeons)
{
LogDebug($"Removed inactive dungeon of tier {dungeon.TierName} at grid {dungeon.Grid}.");
}
}
}
);
}
private void StartTimerUpdateMarkers()
{
_updateMarkersTimer = timer.Every(30f, UpdateMarkers);
}
private void UpdateMarkers()
{
var mapMarkers = UnityEngine.Object.FindObjectsOfType<MapMarkerGenericRadius>();
foreach (var marker in mapMarkers.Where(marker => marker.IsValid()))
{
marker.SendUpdate();
}
}
private void AutoSpawnDungeon(BasePlayer player = null)
{
if (_dungeonData.ActiveDungeons.Count >= _configData.DungeonSpawn.MaxTotalActiveDungeons)
{
LogDebug(lang.GetMessage("MaxActiveDungeonsReached", this));
player?.ChatMessage(Msg(player.UserIDString, "MaxActiveDungeonsReached"));
}
else if (TryFindDungeonSpawnPoint(out var position, out var rotation))
{
LogDebug(lang.GetMessage("AttemptSpawnDungeons", this));
player?.ChatMessage(Msg(player.UserIDString, "AttemptSpawnDungeons"));
CreateDungeon(position, rotation, player);
}
else
{
LogDebug(lang.GetMessage("DungeonSpawnFailed", this));
player?.ChatMessage(Msg(player.UserIDString, "DungeonSpawnFailed"));
}
}
private void CreateDungeon(Vector3 position, Quaternion rotation, BasePlayer player = null)
{
string prefabPath = GetRandomDungeonPrefab();
if (!ValidatePrefabPath(prefabPath))
return;
if (!(GameManager.server.CreateEntity(prefabPath, position, rotation) is BasePortal dungeonPortal))
{
LogDebug("Failed to create dungeon portal.");
return;
}
var grid = PhoneController.PositionToGridCoord(position);
InitializeDungeonPortal(dungeonPortal, position, grid);
var proceduralDungeon = GetProceduralDungeon(dungeonPortal);
if (!ValidateProceduralDungeon(proceduralDungeon, dungeonPortal, grid))
return;
var selectedTier = GetDungeonTier(proceduralDungeon.spawnedCells.Count);
LogDebug($"Selected dungeon tier: {selectedTier} at grid: {grid}.");
var activeDungeon = RegisterActiveDungeon(dungeonPortal, proceduralDungeon, position, grid, selectedTier);
LogDebug($"Creating dungeon with ID: {activeDungeon.PortalId} at grid {grid}.");
ServerMgr.Instance.StartCoroutine(PopulateDungeonCoroutine(proceduralDungeon, activeDungeon, player));
}
private bool ValidatePrefabPath(string prefabPath)
{
if (!string.IsNullOrEmpty(prefabPath))
return true;
LogDebug("No prefab path available for dungeon spawning.");
return false;
}
private void InitializeDungeonPortal(BasePortal dungeonPortal, Vector3 position, string grid)
{
dungeonPortal.OwnerID = OwnerID;
dungeonPortal.Spawn();
LogDebug($"Trying to spawn Dungeon at grid: {grid}.");
}
private bool ValidateProceduralDungeon(ProceduralDynamicDungeon proceduralDungeon, BasePortal dungeonPortal, string grid)
{
if (proceduralDungeon == null)
{
LogDebug($"ProceduralDynamicDungeon not found, destroying the Dungeon at grid: {grid}.");
dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
return false;
}
if (proceduralDungeon.spawnedCells.Count < 1)
{
LogDebug($"No cells found, destroying the Dungeon at grid: {grid}.");
dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
return false;
}
return true;
}
private ActiveDungeon RegisterActiveDungeon(BasePortal dungeonPortal, ProceduralDynamicDungeon proceduralDungeon, Vector3 position, string grid, DungeonTierConfig selectedTier)
{
var activeDungeon = new ActiveDungeon
{
PortalId = dungeonPortal.net.ID.Value,
DynamicDungeonId = proceduralDungeon.net.ID.Value,
Position = position,
Grid = grid,
TierName = _tierNameMap[selectedTier],
TierConfig = selectedTier,
EntityIds = new List<ulong>(),
Spawned = false,
};
_dungeonData.ActiveDungeons.Add(activeDungeon);
return activeDungeon;
}
private void LogDebug(string message)
{
if (_configData.EnableDebug)
Puts(message);
}
private IEnumerator PopulateDungeonCoroutine(ProceduralDynamicDungeon proceduralDungeon, ActiveDungeon activeDungeon, BasePlayer player = null)
{
yield return new WaitForSeconds(3f);
LogDebug($"Starting to populate dungeon ID: {activeDungeon.PortalId}, Tier: {activeDungeon.TierName} at grid: {activeDungeon.Grid}.");
RemoveOriginalEntities(proceduralDungeon);
var allSpawnEntries = GenerateSpawnEntries(activeDungeon.TierConfig);
LogDebug($"Generated {allSpawnEntries.Count} spawn entries.");
Dictionary<Vector3, List<string>> occupiedPositions = new Dictionary<Vector3, List<string>>();
bool validSpawnLocationsExist = true;
foreach (var entry in allSpawnEntries)
{
var validLocations = GetAvailableSpawnLocations(proceduralDungeon, entry.prefab.resourcePath);
if (validLocations.Count == 0)
{
LogDebug($"No valid spawn locations found for prefab: {entry.prefab.resourcePath}");
validSpawnLocationsExist = false;
break;
}
}
if (!validSpawnLocationsExist)
{
player?.ChatMessage("No valid spawn locations available for some or all prefabs. Cancelling spawn.");
LogDebug("No valid spawn locations available for some or all prefabs. Cancelling spawn.");
yield break;
}
foreach (var entry in allSpawnEntries)
{
var validLocations = GetAvailableSpawnLocations(proceduralDungeon, entry.prefab.resourcePath);
string entityType =
entry.prefab.resourcePath.Contains("autoturret") ? "AutoTurret"
: entry.prefab.resourcePath.Contains("woodenbox") ? "StorageContainer"
: entry.prefab.resourcePath.Contains("scientist") ? "NPC"
: "Other";
bool placed = false;
var freeLocations = validLocations.Where(loc => !occupiedPositions.ContainsKey(loc.Item1)).ToList();
var locationsToTry = freeLocations.Any() ? freeLocations : validLocations;
foreach (var (position, rotation) in locationsToTry)
{
if (occupiedPositions.TryGetValue(position, out var existingTypes))
{
if (existingTypes.Contains(entityType))
{
LogDebug($"Conflict: Cannot place another {entityType} at {position}. Trying another position...");
continue;
}
if ((entityType == "AutoTurret" && existingTypes.Contains("StorageContainer")) || (entityType == "StorageContainer" && existingTypes.Contains("AutoTurret")))
{
LogDebug($"Conflict: Cannot place {entityType} at {position} because {string.Join(", ", existingTypes)} already present. Trying another position...");
continue;
}
}
var entity = GameManager.server.CreateEntity(entry.prefab.resourcePath, position, rotation);
if (entity != null)
{
entity.OwnerID = OwnerID;
entity.Spawn();
InitializeEntity(entity, activeDungeon);
if (!occupiedPositions.ContainsKey(position))
{
occupiedPositions[position] = new List<string>();
}
occupiedPositions[position].Add(entityType);
yield return new WaitForSeconds(0.5f);
if (entity.IsDestroyed || !entity.isSpawned)
{
LogDebug($"Entity {entry.prefab.resourcePath} was destroyed after spawn at {position}.");
}
else
{
LogDebug($"Placed entity {entity.ShortPrefabName} at {position}.");
}
placed = true;
break;
}
}
if (!placed)
{
var (fallbackPosition, fallbackRotation) = validLocations[Random.Range(0, validLocations.Count)];
LogDebug($"Fallback: Placing {entityType} at {fallbackPosition} despite conflicts.");
var entity = GameManager.server.CreateEntity(entry.prefab.resourcePath, fallbackPosition, fallbackRotation);
if (entity != null)
{
entity.OwnerID = OwnerID;
entity.Spawn();
InitializeEntity(entity, activeDungeon);
if (!occupiedPositions.ContainsKey(fallbackPosition))
{
occupiedPositions[fallbackPosition] = new List<string>();
}
occupiedPositions[fallbackPosition].Add(entityType);
yield return new WaitForSeconds(0.5f);
if (entity.IsDestroyed || !entity.isSpawned)
{
LogDebug($"Entity {entry.prefab.resourcePath} was destroyed after spawn at {fallbackPosition}.");
}
else
{
LogDebug($"Placed entity {entity.ShortPrefabName} at {fallbackPosition} as a fallback.");
}
}
}
}
LogDebug($"Finished placing entities. Total placed: {activeDungeon.EntityIds.Count}/{allSpawnEntries.Count}.");
CreateDungeonMarkers(activeDungeon);
NotifyPlayersOfNewDungeon(activeDungeon);
LogDebug($"Dungeon spawned successfully at grid: {activeDungeon.Grid}.");
activeDungeon.Spawned = true;
player?.ChatMessage(Msg(player.UserIDString, "DungeonSpawnedSuccess", activeDungeon.Grid));
}
private List<(Vector3 Position, Quaternion Rotation)> GetAvailableSpawnLocations(ProceduralDynamicDungeon proceduralDungeon, string prefabPath)
{
GameObject prefab = GameManager.server.FindPrefab(prefabPath);
if (prefab == null)
{
Puts($"Prefab not found: {prefabPath}");
return new List<(Vector3, Quaternion)>();
}
BaseEntity prefabEntity = prefab.GetComponent<BaseEntity>();
if (prefabEntity == null)
{
Puts($"Prefab does not have a BaseEntity: {prefabPath}");
return new List<(Vector3, Quaternion)>();
}
return proceduralDungeon
.spawnedCells.Where(cell => cell != null)
.SelectMany(cell => cell.spawnGroups)
.Where(group => group != null)
.SelectMany(group => group.spawnPoints)
.Where(spawnPoint => spawnPoint != null && spawnPoint.IsAvailableTo(prefab))
.Select(spawnPoint =>
{
spawnPoint.GetLocation(out var position, out var rotation);
return (position, rotation);
})
.ToList();
}
private void InitializeEntity(BaseEntity entity, ActiveDungeon activeDungeon)
{
if (entity != null)
{
activeDungeon.EntityIds.Add(entity.net.ID.Value);
if (entity is NPCPlayer)
{
activeDungeon.NpcIds.Add(entity.net.ID.Value);
}
else if (entity is AutoTurret turret)
{
ConfigureTurret(turret, activeDungeon.TierConfig.AutoTurretConfig);
}
else if (entity is StorageContainer storage)
{
ConfigureStorage(storage);
}
}
}
private void RemoveOriginalEntities(ProceduralDynamicDungeon proceduralDungeon)
{
foreach (var cell in proceduralDungeon.spawnedCells)
{
foreach (var group in cell.spawnGroups)
{
for (int i = group.spawnInstances.Count - 1; i >= 0; i--)
{
SpawnPointInstance spawnPointInstance = group.spawnInstances[i];
BaseEntity entity = spawnPointInstance?.gameObject?.ToBaseEntity();
if (entity != null && !IsDungeonEntity(entity) && (entity is NPCDwelling || entity is ScarecrowNPC || entity is LootContainer))
{
LogDebug($"Removing original entity: {entity.ShortPrefabName} at {entity.transform.position}");
entity.Kill();
}
}
}
}
}
private List<SpawnGroup.SpawnEntry> GenerateSpawnEntries(DungeonTierConfig tierConfig)
{
var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();
AddNpcEntries(tierConfig, allSpawnEntries);
AddTurretEntries(tierConfig, allSpawnEntries);
AddLootEntries(tierConfig, allSpawnEntries);
return allSpawnEntries.OrderBy(_ => Random.value).ToList();
}
private void AddNpcEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
{
foreach (var config in tierConfig.NpcSpawnConfigs)
{
if (GameManifest.pathToGuid.TryGetValue(config.PrefabName, out var guid))
{
spawnEntries.AddRange(
Enumerable.Repeat(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = guid },
weight = 1,
mobile = true,
},
config.Total
)
);
}
}
}
private void AddTurretEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
{
var prefabName = "assets/prefabs/npc/autoturret/autoturret_deployed.prefab";
if (GameManifest.pathToGuid.TryGetValue(prefabName, out var turretGuid))
{
spawnEntries.AddRange(
Enumerable.Repeat(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = turretGuid },
weight = 1,
mobile = false,
},
tierConfig.AutoTurretConfig.Total
)
);
}
}
private void AddLootEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
{
var prefabName = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab";
if (GameManifest.pathToGuid.TryGetValue(prefabName, out var boxGuid))
{
spawnEntries.AddRange(
Enumerable.Repeat(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = boxGuid },
weight = 1,
mobile = false,
},
tierConfig.TotalLootBoxes
)
);
}
}
private void ConfigureTurret(AutoTurret turret, TurretConfig turretConfig)
{
turret.InitializeHealth(turretConfig.Health, turretConfig.Health);
var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
weapon?.MoveToContainer(turret.inventory, 0);
var laserSight = ItemManager.CreateByName("weapon.mod.lasersight");
laserSight?.MoveToContainer(weapon.contents);
turret.UpdateAttachedWeapon();
turret.gameObject.AddComponent<TurretMonoBehaviour>();
}
public class TurretMonoBehaviour : MonoBehaviour
{
private AutoTurret _turret;
private void Awake()
{
_turret = GetComponent<AutoTurret>();
_turret.SetPeacekeepermode(false);
_turret.InitiateStartup();
_turret.isLootable = false;
_turret.dropFloats = false;
_turret.dropsLoot = false;
InvokeRepeating(nameof(RefillAmmo), 2f, 30f);
}
private void RefillAmmo()
{
int maxReserveAmmo = 1000;
if (_turret.AttachedWeapon is not BaseProjectile baseProjectile || baseProjectile.primaryMagazine?.ammoType == null)
return;
var ammoType = baseProjectile.primaryMagazine.ammoType;
int currentAmmoCount = _turret.inventory.itemList.Where(item => item.info == ammoType).Sum(item => item.amount);
if (currentAmmoCount < maxReserveAmmo)
{
int ammoNeeded = maxReserveAmmo - currentAmmoCount;
Item ammoItem = ItemManager.Create(ammoType, ammoNeeded);
ammoItem?.MoveToContainer(_turret.inventory);
_turret.UpdateTotalAmmo();
_turret.EnsureReloaded();
_turret.SendNetworkUpdateImmediate();
}
}
private void OnDestroy()
{
CancelInvoke(nameof(RefillAmmo));
}
}
private void ConfigureStorage(StorageContainer storage)
{
storage.skinID = _configData.LootBoxConfig.SmallWoodBoxSkinID;
AddCodeLock(storage);
PopulateStorage(storage.inventory, _configData.LootBoxConfig.LootItems);
}
private void AddCodeLock(StorageContainer storage)
{
var prefabName = "assets/prefabs/locks/keypad/lock.code.prefab";
var codeLock = GameManager.server.CreateEntity(prefabName) as CodeLock;
if (codeLock == null)
return;
codeLock.SetParent(storage, storage.GetSlotAnchorName(BaseEntity.Slot.Lock));
codeLock.Spawn();
codeLock.code = Random.Range(1000, 9999).ToString();
codeLock.hasCode = true;
codeLock.SetFlag(BaseEntity.Flags.Locked, true);
}
private void PopulateStorage(ItemContainer container, List<ItemConfig> lootItems)
{
var shuffledItems = lootItems.OrderBy(_ => Random.value).ToList();
var differentItemsCount = 0;
foreach (var itemConfig in shuffledItems)
{
if (differentItemsCount >= _configData.LootBoxConfig.MaxDifferentItemsPerBox)
{
break;
}
if (Random.value * 100f <= itemConfig.InclusionChancePercentage)
{
var itemDefinition = ItemManager.FindItemDefinition(itemConfig.ShortName);
if (itemDefinition != null)
{
var amount = Random.Range(itemConfig.MinimumAmount, itemConfig.MaximumAmount + 1);
if (container.itemList.Count < container.capacity)
{
var item = ItemManager.Create(itemDefinition, amount);
item.MoveToContainer(container);
differentItemsCount++;
}
}
}
}
}
private List<ActiveDungeon> RemoveInactiveDungeons()
{
var removedDungeons = new List<ActiveDungeon>();
foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
{
if (CanBeRemoved(dungeon))
{
removedDungeons.Add(dungeon);
DestroyDungeon(dungeon);
}
}
SaveData();
return removedDungeons;
}
private void DestroyDungeon(ActiveDungeon dungeon)
{
var dungeonEntity = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.PortalId)) as HalloweenDungeon;
if (dungeonEntity != null)
{
dungeonEntity?.Kill(BaseNetworkable.DestroyMode.None);
}
var proceduralDynamicDungeon = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.DynamicDungeonId)) as ProceduralDynamicDungeon;
if (proceduralDynamicDungeon != null)
{
RemoveLootableCorpse(proceduralDynamicDungeon);
RemoveItemDrop(proceduralDynamicDungeon);
RemoveDroppedItemContainer(proceduralDynamicDungeon);
}
DestroyEntities(dungeon);
RemoveDungeonMarkers(dungeon);
_dungeonData.ActiveDungeons.Remove(dungeon);
}
private void RemoveDroppedItemContainer(ProceduralDynamicDungeon dungeon)
{
List<DroppedItemContainer> obj = Pool.Get<List<DroppedItemContainer>>();
Vis.Entities(dungeon.transform.position, 80f, obj);
foreach (DroppedItemContainer item in obj)
{
if (item.IsValid() && !item.IsDestroyed)
{
item?.Kill();
}
}
Pool.FreeUnmanaged(ref obj);
}
private void RemoveItemDrop(ProceduralDynamicDungeon dungeon)
{
List<DroppedItem> obj = Pool.Get<List<DroppedItem>>();
Vis.Entities(dungeon.transform.position, 80f, obj);
foreach (DroppedItem item in obj)
{
if (item.IsValid() && !item.IsDestroyed)
{
item?.Kill();
}
}
Pool.FreeUnmanaged(ref obj);
}
private void RemoveLootableCorpse(ProceduralDynamicDungeon dungeon)
{
List<LootableCorpse> obj = Pool.Get<List<LootableCorpse>>();
Vis.Entities(dungeon.transform.position, 80f, obj);
foreach (LootableCorpse item in obj)
{
if (item.IsValid() && !item.IsDestroyed)
{
item?.Kill();
}
}
Pool.FreeUnmanaged(ref obj);
}
private void DestroyEntities(ActiveDungeon dungeon)
{
foreach (var entityId in dungeon.EntityIds)
{
var entity = BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as BaseEntity;
entity?.Kill(BaseNetworkable.DestroyMode.None);
}
dungeon.EntityIds.Clear();
}
private void RemoveDungeonMarkers(ActiveDungeon dungeon)
{
foreach (var marker in _dungeonData.ActiveDungeonMarkers.Where(m => m.Position == dungeon.Position).ToList())
{
var vendingMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.VendingMarker)) as BaseEntity;
var radiusMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.RadiusMarker)) as BaseEntity;
vendingMarker?.Kill(BaseNetworkable.DestroyMode.None);
radiusMarker?.Kill(BaseNetworkable.DestroyMode.None);
_dungeonData.ActiveDungeonMarkers.Remove(marker);
}
}
private IEnumerable<ActiveDungeon> RemoveAllDungeons()
{
var removedDungeons = new List<ActiveDungeon>(_dungeonData.ActiveDungeons);
foreach (var dungeon in removedDungeons)
{
DestroyDungeon(dungeon);
}
_dungeonData = new DungeonData();
SaveData();
return removedDungeons;
}
#endregion
#region Utility Methods
private ProceduralDynamicDungeon GetProceduralDungeon(BasePortal dungeonPortal)
{
return (dungeonPortal as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeonPortal as HalloweenDungeon)?.dungeonInstance.Get(true);
}
private void CleanupExistingEntities()
{
foreach (
var entityType in new Type[]
{
typeof(VendingMachineMapMarker),
typeof(MapMarkerGenericRadius),
typeof(BasePortal),
typeof(ProceduralDynamicDungeon),
typeof(AutoTurret),
typeof(NPCPlayer),
typeof(StorageContainer),
}
)
{
foreach (var obj in UnityEngine.Object.FindObjectsOfType(entityType))
{
if (obj is BaseEntity baseEntity && baseEntity.OwnerID == OwnerID)
{
baseEntity.Kill(BaseNetworkable.DestroyMode.None);
}
}
}
_dungeonData = new DungeonData();
}
private void InitializeTierNameMap()
{
_tierNameMap = new Dictionary<DungeonTierConfig, string>
{
{ _configData.Tiers.Easy, "Easy" },
{ _configData.Tiers.Normal, "Normal" },
{ _configData.Tiers.Medium, "Medium" },
{ _configData.Tiers.Hard, "Hard" },
{ _configData.Tiers.Nightmare, "Nightmare" },
};
}
private void LoadData()
{
_dataFile = Interface.Oxide.DataFileSystem.GetFile("Dungeons_Data");
_dungeonData = _dataFile.ReadObject<DungeonData>() ?? new DungeonData();
}
private void SaveData()
{
_dataFile.WriteObject(_dungeonData);
}
private static bool IsPlayer(BasePlayer targetPlayer) => targetPlayer != null && targetPlayer.userID.IsSteamId();
private void NotifyPlayersOfNewDungeon(ActiveDungeon activeDungeon)
{
foreach (var player in BasePlayer.activePlayerList)
{
var message = Msg(player.UserIDString, "DungeonAppeared", activeDungeon.TierName, activeDungeon.Grid);
player.ChatMessage(message);
player.ShowToast(GameTip.Styles.Blue_Normal, message);
}
}
private void NotifyPlayerOnRemoval(BasePlayer player, IEnumerable<ActiveDungeon> removedDungeons)
{
if (!removedDungeons.Any())
{
player.ChatMessage(Msg(player.UserIDString, "NoInactiveDungeonsToRemove"));
return;
}
foreach (var dungeon in removedDungeons)
{
player.ChatMessage(Msg(player.UserIDString, "DungeonRemovedDetail", dungeon.TierName, dungeon.Grid));
}
}
private bool IsAdmin(BasePlayer player) => player != null && player.IsAdmin;
private bool IsDungeonEntity(BaseEntity entity) => entity != null && _dungeonData.ActiveDungeons.Any(d => d.EntityIds.Contains(entity.net.ID.Value));
private string GetRandomDungeonPrefab()
{
var prefabs = new List<string>();
if (_configData.DungeonSpawn.EnableXmasDungeon)
prefabs.Add("assets/prefabs/missions/portal/xmasportalentry.prefab");
if (_configData.DungeonSpawn.EnableHalloweenDungeon)
prefabs.Add("assets/prefabs/missions/portal/halloweenportalentry.prefab");
return prefabs.Count > 0 ? prefabs[Random.Range(0, prefabs.Count)] : null;
}
private static bool IsValidPosition(Vector3 position, out Vector3 suitablePosition, out Quaternion suitableRotation)
{
suitablePosition = Vector3.zero;
suitableRotation = Quaternion.identity;
if (!LocationCheck.GetTerrainInfo(position, out var hitInfo))
return false;
if (LocationCheck.InsideRock(position, 10f))
return false;
if (LocationCheck.InWater(position))
return false;
if (LocationCheck.OnRoadOrRail(position))
return false;
if (LocationCheck.InNoBuildZone(position, 10f))
return false;
return SetSuitablePositionAndRotation(position, hitInfo, out suitablePosition, out suitableRotation);
}
private static bool SetSuitablePositionAndRotation(Vector3 position, RaycastHit hitInfo, out Vector3 suitablePosition, out Quaternion suitableRotation)
{
suitablePosition = hitInfo.point;
suitableRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
return true;
}
private void CreateDungeonMarkers(ActiveDungeon activeDungeon)
{
var prefabVendingMarker = "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab";
var vendingMarker = CreateMapMarker<VendingMachineMapMarker>(prefabVendingMarker, activeDungeon.Position);
vendingMarker.OwnerID = OwnerID;
vendingMarker.markerShopName = $"Dungeon: {activeDungeon.TierName}";
vendingMarker.SendNetworkUpdate();
var prefabRadiusMarker = "assets/prefabs/tools/map/genericradiusmarker.prefab";
var radiusMarker = CreateMapMarker<MapMarkerGenericRadius>(prefabRadiusMarker, activeDungeon.Position);
radiusMarker.OwnerID = OwnerID;
radiusMarker.alpha = 0.75f;
radiusMarker.radius = 0.5f;
radiusMarker.color2 = GetMarkerColorByTier(activeDungeon.TierName);
radiusMarker.SendUpdate();
radiusMarker.SendNetworkUpdate();
_dungeonData.ActiveDungeonMarkers.Add(
new DungeonMarker
{
VendingMarker = vendingMarker.net.ID.Value,
RadiusMarker = radiusMarker.net.ID.Value,
Position = activeDungeon.Position,
Tier = activeDungeon.TierName,
}
);
}
private T CreateMapMarker<T>(string prefab, Vector3 position)
where T : BaseEntity
{
var entity = GameManager.server.CreateEntity(prefab, position) as T;
entity?.Spawn();
return entity;
}
private static Color GetMarkerColorByTier(string tierName)
{
return tierName switch
{
"Easy" => Color.green,
"Normal" or "Medium" => Color.yellow,
"Hard" => Color.red,
"Nightmare" => Color.black,
_ => Color.white,
};
}
private bool CanBeRemoved(ActiveDungeon dungeon)
{
return AreAllNpcsGone(dungeon) && NoPlayersInside(dungeon) && dungeon.Spawned;
}
private bool AreAllNpcsGone(ActiveDungeon dungeon)
{
return dungeon.NpcIds.All(npcId =>
{
var npc = BaseNetworkable.serverEntities.Find(new NetworkableId(npcId)) as BaseEntity;
return npc == null || npc.IsDestroyed;
});
}
private bool NoPlayersInside(ActiveDungeon dungeon)
{
var dynamicDungeon = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.DynamicDungeonId)) as ProceduralDynamicDungeon;
return dynamicDungeon == null || !dynamicDungeon.ContainsAnyPlayers();
}
private bool TryFindDungeonSpawnPoint(out Vector3 position, out Quaternion rotation)
{
var mapSize = World.Size;
for (var attempt = 0; attempt < 1000; attempt++)
{
var candidatePosition = new Vector3(Random.Range(-mapSize / 2, mapSize / 2), 0, Random.Range(-mapSize / 2, mapSize / 2));
if (IsValidPosition(candidatePosition, out position, out rotation) && IsSafeFromOtherDungeons(position) && !IsCloseToMonuments(position))
{
return true;
}
}
position = Vector3.zero;
rotation = Quaternion.identity;
return false;
}
private bool IsSafeFromOtherDungeons(Vector3 position)
{
return _dungeonData.ActiveDungeons.All(dungeon => Vector3.Distance(position, dungeon.Position) >= _configData.DungeonSpawn.MinDistanceBetweenDungeons);
}
private bool IsCloseToMonuments(Vector3 position)
{
return TerrainMeta.Path.Monuments.Any(mon => Vector3.Distance(position, mon.transform.position) < _configData.DungeonSpawn.MinDistanceFromMonuments);
}
private DungeonTierConfig GetDungeonTier(int cellCount)
{
return cellCount switch
{
< 5 => _configData.Tiers.Easy,
< 8 => _configData.Tiers.Normal,
< 12 => _configData.Tiers.Medium,
< 16 => _configData.Tiers.Hard,
_ => _configData.Tiers.Nightmare,
};
}
#endregion
#region Configuration
private class ConfigData
{
[JsonProperty(PropertyName = "Dungeon Spawn Settings")]
public DungeonSpawnSettings DungeonSpawn { get; set; } = new DungeonSpawnSettings();
[JsonProperty(PropertyName = "Tiers")]
public DungeonTiers Tiers { get; set; } = new DungeonTiers();
[JsonProperty(PropertyName = "Loot Box Config")]
public LootBoxConfig LootBoxConfig { get; set; } = new LootBoxConfig();
[JsonProperty("Enable Debug")]
public bool EnableDebug { get; set; } = true;
[JsonProperty(PropertyName = "Version")]
public VersionNumber Version { get; set; }
}
private class DungeonTiers
{
[JsonProperty(PropertyName = "Easy")]
public DungeonTierConfig Easy { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 1,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 1 },
},
AutoTurretConfig = new TurretConfig
{
Total = 1,
Health = 300,
WeaponShortName = "pistol.revolver",
},
};
[JsonProperty(PropertyName = "Normal")]
public DungeonTierConfig Normal { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 2,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 3 },
},
AutoTurretConfig = new TurretConfig
{
Total = 2,
Health = 600,
WeaponShortName = "smg.2",
},
};
[JsonProperty(PropertyName = "Medium")]
public DungeonTierConfig Medium { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 4,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 4 },
},
AutoTurretConfig = new TurretConfig
{
Total = 3,
Health = 1000,
WeaponShortName = "smg.mp5",
},
};
[JsonProperty(PropertyName = "Hard")]
public DungeonTierConfig Hard { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 5,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 4 },
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 3 },
},
AutoTurretConfig = new TurretConfig
{
Total = 4,
Health = 1500,
WeaponShortName = "rifle.ak",
},
};
[JsonProperty(PropertyName = "Nightmare")]
public DungeonTierConfig Nightmare { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 6,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 5 },
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 5 },
},
AutoTurretConfig = new TurretConfig
{
Total = 6,
Health = 2000,
WeaponShortName = "rifle.ak",
},
};
}
private class DungeonTierConfig
{
[JsonProperty(PropertyName = "Maximum Loot Boxes")]
public int TotalLootBoxes { get; set; }
[JsonProperty(PropertyName = "NPC Spawn Configs", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<NpcSpawnConfig> NpcSpawnConfigs { get; set; }
[JsonProperty(PropertyName = "Auto Turret Config")]
public TurretConfig AutoTurretConfig { get; set; } = new TurretConfig();
}
public class LootBoxConfig
{
[JsonProperty(PropertyName = "Small Wood Box Skin ID")]
public ulong SmallWoodBoxSkinID { get; set; } = 2998755525;
[JsonProperty(PropertyName = "Max Different Items Per Box")]
public int MaxDifferentItemsPerBox { get; set; } = 6;
[JsonProperty(PropertyName = "Loot Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<ItemConfig> LootItems { get; set; } =
new List<ItemConfig>
{
new ItemConfig
{
ShortName = "ammo.rifle",
InclusionChancePercentage = 15,
MinimumAmount = 100,
MaximumAmount = 300,
},
};
}
private class NpcSpawnConfig
{
[JsonProperty(PropertyName = "Prefab")]
public string PrefabName { get; set; }
[JsonProperty(PropertyName = "Maximum")]
public int Total { get; set; }
}
private class TurretConfig
{
[JsonProperty(PropertyName = "Maximum")]
public int Total { get; set; } = 3;
[JsonProperty(PropertyName = "Health")]
public float Health { get; set; } = 1000f;
[JsonProperty(PropertyName = "Weapon Short Name")]
public string WeaponShortName { get; set; } = "rifle.ak";
}
private class DungeonSpawnSettings
{
[JsonProperty(PropertyName = "Enable Auto Spawn")]
public bool EnableAutoSpawn { get; set; } = true;
[JsonProperty(PropertyName = "Auto Spawn Cycle Interval")]
public float AutoSpawnCycleInterval { get; set; } = 60f;
[JsonProperty(PropertyName = "Enable Xmas Dungeon")]
public bool EnableXmasDungeon { get; set; } = true;
[JsonProperty(PropertyName = "Enable Halloween Dungeon")]
public bool EnableHalloweenDungeon { get; set; } = true;
[JsonProperty(PropertyName = "Max Total Active Dungeons")]
public int MaxTotalActiveDungeons { get; set; } = 4;
[JsonProperty(PropertyName = "Min Distance From Monuments")]
public float MinDistanceFromMonuments { get; set; } = 100f;
[JsonProperty(PropertyName = "Min Distance Between Dungeons")]
public float MinDistanceBetweenDungeons { get; set; } = 100f;
}
public class ItemConfig
{
[JsonProperty(PropertyName = "ShortName")]
public string ShortName { get; set; }
[JsonProperty(PropertyName = "Inclusion Chance Percentage")]
public float InclusionChancePercentage { get; set; }
[JsonProperty(PropertyName = "Minimum Amount")]
public int MinimumAmount { get; set; }
[JsonProperty(PropertyName = "Maximum Amount")]
public int MaximumAmount { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_configData = Config.ReadObject<ConfigData>() ?? new ConfigData();
}
catch (Exception ex)
{
PrintError($"The configuration file is corrupt: {ex}");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
PrintWarning("Creating a new configuration file.");
_configData = new ConfigData { Version = Version };
}
protected override void SaveConfig()
{
Config.WriteObject(_configData, true);
}
#endregion
#region Localization
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(
new Dictionary<string, string>
{
["NoInactiveDungeonsToRemove"] = "There are no inactive dungeons to remove.",
["NoDungeonsToRemove"] = "There are no dungeons to remove.",
["DungeonRemovedDetail"] = "Removed dungeon of tier {0} at grid {1}.",
["AttemptSpawnDungeons"] = "Attempting to spawn dungeons.",
["DungeonAppeared"] = "A {0} Dungeon has appeared at grid {1}!",
["DungeonSpawnFailed"] = "Could not find a valid location to spawn a new dungeon.",
["DungeonSpawnedSuccess"] = "Dungeon spawned successfully at grid {0}!",
["MaxActiveDungeonsReached"] = "Cannot spawn: Maximum number of active dungeons reached.",
},
this
);
}
private string Msg(string userId, string key, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
#endregion
#region Data Classes
private class DungeonData
{
public List<ActiveDungeon> ActiveDungeons { get; set; } = new List<ActiveDungeon>();
public List<DungeonMarker> ActiveDungeonMarkers { get; set; } = new List<DungeonMarker>();
}
private class ActiveDungeon
{
public ulong PortalId { get; set; }
public ulong DynamicDungeonId { get; set; }
public Vector3 Position { get; set; }
public string Grid { get; set; }
public string TierName { get; set; }
public DungeonTierConfig TierConfig { get; set; }
public List<ulong> EntityIds { get; set; } = new List<ulong>();
public List<ulong> NpcIds { get; set; } = new List<ulong>();
public bool Spawned { get; set; } = false;
}
private class DungeonMarker
{
public ulong VendingMarker { get; set; }
public ulong RadiusMarker { get; set; }
public Vector3 Position { get; set; }
public string Tier { get; set; }
}
#endregion
#region Collider Checks
public static class LocationCheck
{
public static bool InsideRock(Vector3 position, float radius)
{
List<Collider> colliders = Pool.GetList<Collider>();
Vis.Colliders(position, radius, colliders, Layers.Mask.World, QueryTriggerInteraction.Ignore);
bool isInsideRock = colliders.Any(collider =>
collider.name.Contains("rock", StringComparison.OrdinalIgnoreCase)
|| collider.name.Contains("cliff", StringComparison.OrdinalIgnoreCase)
|| collider.name.Contains("formation", StringComparison.OrdinalIgnoreCase)
);
Pool.FreeList(ref colliders);
return isInsideRock;
}
public static bool GetTerrainInfo(Vector3 position, out RaycastHit hitInfo)
{
return Physics.Linecast(position + Vector3.up, position - Vector3.up, out hitInfo, Layers.Mask.Terrain);
}
public static bool InWater(Vector3 position) => WaterLevel.Test(position, false, false);
public static bool OnRoadOrRail(Vector3 position) => (TerrainMeta.TopologyMap.GetTopology(position) & (int)TerrainTopology.Enum.Road) != 0;
public static bool InNoBuildZone(Vector3 position, float radius) => Physics.CheckSphere(position, radius, Layers.Mask.Prevent_Building, QueryTriggerInteraction.Ignore);
}
#endregion
}
}
It seems you've shared a substantial piece of code that represents a Rust plugin called "Dungeons." This plugin appears to create and manage procedural dungeons within the game Rust, offering features like spawning dungeons, handling NPCs, turrets, loot boxes, and integrating with game events. If you have specific questions about the code or need assistance with particular aspects—such as understanding how certain functions work, troubleshooting issues, or extending functionality—please let me know! I'd be happy to help you!
b5592f3d548b6dfbd297f4d3quero criar novas variáveis que armazenem :
typeof(VendingMachineMapMarker),
typeof(MapMarkerGenericRadius),
typeof(BasePortal),
typeof(ProceduralDynamicDungeon),
typeof(AutoTurret),
typeof(NPCPlayer),
typeof(StorageContainer),
para não precisar ficar pegando e buscando elas, e otimizar as partes do código que buscam para usar diretoPara otimizar o seu código e evitar chamadas repetidas ao `typeof(...)`, você pode armazenar os tipos em variáveis estáticas, ou em uma lista, e usá-los sempre que necessário.
Vou dar um exemplo de como você pode criar uma estrutura para armazenar esses tipos de forma que eles possam ser usados de forma rápida e eficiente. Um dos lugares apropriados para fazer isso é dentro de uma classe privada que você pode adicionar ao seu plugin.
### Exemplo de Implementação:
Dentro da sua classe Dungeons, adicione uma propriedade estática que armazena os tipos que você mencionou:
```csharp
private static readonly Type[] EntityTypes = new Type[]
{
typeof(VendingMachineMapMarker),
typeof(MapMarkerGenericRadius),
typeof(BasePortal),
typeof(ProceduralDynamicDungeon),
typeof(AutoTurret),
typeof(NPCPlayer),
typeof(StorageContainer)
};
```
### Uso:
Agora, em vez de usar `typeof(...)`, você pode simplesmente referenciar o array `EntityTypes`. Isso simplifica e melhora a legibilidade do código:
```csharp
private void CleanupExistingEntities()
{
foreach (var entityType in EntityTypes)
{
foreach (var obj in UnityEngine.Object.FindObjectsOfType(entityType))
{
if (obj is BaseEntity baseEntity && baseEntity.OwnerID == OwnerID)
{
baseEntity.Kill(BaseNetworkable.DestroyMode.None);
}
}
}
_dungeonData = new DungeonData();
}
```
### Vantagens:
1. **Desempenho**: O uso de um array de tipos evita chamadas repetidas ao `typeof(...)`, que é uma operação relativamente custosa.
2. **Legibilidade**: Facilita a leitura do código, já que a intenção está mais clara, e você centraliza a definição dos tipos.
3. **Manutenção**: Caso você precise adicionar ou remover tipos no futuro, você só precisa alterar o array em um único lugar.
Você pode aplicar essa estrutura a outras partes do seu código onde você faz uso repetido de `typeof(...)`. Isso deve resultar em um código mais otimizado e facilmente gerenciável. Se precisar de mais ajuda para refatorar outras partes do código, fique à vontade para pedir!d6e3091a060d135f59b4bf0dvocê entendeu errado, quero armazenar elas em si: VendingMachineMapMarker MapMarkerGenericRadius BasePortal ProceduralDynamicDungeon AutoTurret NPCPlayer StorageContainer e no uso nas funções usar desse armazenamento, uma variável no caso
Entendi! Você deseja armazenar as classes em variáveis, de modo que você possa usar essas variáveis em vez de fazer referência diretamente aos tipos. Isso permitirá que você evite o uso repetido de `typeof(...)` e faça o código mais legível e manageável.
Aqui está um exemplo de como você poderia implementar isso:
### Armazenando os Tipos em Variáveis
Primeiro, você pode criar variáveis privadas do tipo `Type` para armazenar essas classes. Veja como você pode fazer isso dentro da sua classe `Dungeons`:
```csharp
private static readonly Type VendingMachineMapMarkerType = typeof(VendingMachineMapMarker);
private static readonly Type MapMarkerGenericRadiusType = typeof(MapMarkerGenericRadius);
private static readonly Type BasePortalType = typeof(BasePortal);
private static readonly Type ProceduralDynamicDungeonType = typeof(ProceduralDynamicDungeon);
private static readonly Type AutoTurretType = typeof(AutoTurret);
private static readonly Type NPCPlayerType = typeof(NPCPlayer);
private static readonly Type StorageContainerType = typeof(StorageContainer);
```
### Usando as Variáveis nas Funções
Depois de armazenar os tipos, você pode usar essas variáveis em seu código. Aqui está um exemplo da sua função `CleanupExistingEntities`, ajustada para usar as variáveis que você criou:
```csharp
private void CleanupExistingEntities()
{
var entityTypes = new Type[]
{
VendingMachineMapMarkerType,
MapMarkerGenericRadiusType,
BasePortalType,
ProceduralDynamicDungeonType,
AutoTurretType,
NPCPlayerType,
StorageContainerType,
};
foreach (var entityType in entityTypes)
{
foreach (var obj in UnityEngine.Object.FindObjectsOfType(entityType))
{
if (obj is BaseEntity baseEntity && baseEntity.OwnerID == OwnerID)
{
baseEntity.Kill(BaseNetworkable.DestroyMode.None);
}
}
}
_dungeonData = new DungeonData();
}
```
### Vantagens
1. **Legibilidade:** Armazenar os tipos em variáveis com nomes descritivos melhora a legibilidade do código.
2. **Facilidade de Manutenção:** Se você precisar alterar algum tipo, você só precisará alterar em um único lugar.
3. **Redução de Código Duplicado:** Você evita a repetição do `typeof(...)` e torna o código mais limpo.
Agora, sempre que você precisar usar esses tipos, você pode apenas referenciar a variável correspondente, tornando o código mais conciso e fácil de manter. Se você tiver mais alguma dúvida ou precisar de mais esclarecimentos, basta perguntar!824f8991a04bbda741d0268eContext: making a product page in Iptv2live.com website! that sell IPTV subscriptions from differents IPTV Platforms.
Task: Write a compelling product description for an IPTV offer with title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, using best SEO practices for 2024. Follow the structure outlined below and ensure the description is optimized for search engines to help it rank highly on Google. The output must be in the following JSON format:
{
"excerpt": "A concise summary mentioning the main keywords of the post title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.",
"introduction": "Introduction (1-2 sentences): Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.",
"head1": "Shorten my title:'Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free' using semantic keywords".
"body": "5 paragraphs <p></p>"
}
Structure:
excerpt: A concise summary mentioning the main keywords of the post title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.
introduction: Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.
head1: Shorten my title:'Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free' to 3 words max using semantic keywords.
body:
ensure to make the output in 5 paragraphs <p></p> with one h3 and one h4 that includes concise Description of the Key Features of the IPTV subscription from the famous IPTV provider platform named: <b>PROTV</b>. which Includes: 5599 live streaming TV categories, like: UK NEWS, UK GENERAL, BE DOCUMENTAIRE, UK LIGUE 1 PPV, UK PDC BOARD PPV, All, UK NOW SPORT ᴴᴰ, BE BELGIUM DAZN EXCLUSIVE ᴿᴬᵂ, NL HBO MAX PPV, NL FEYENOORD ONE PPV, UK ENTERTAINMENT HEVC, USA, UK DOCUMENTARY, UK NATIONAL LEAGUE, UK ESPN PPV, UK ENTERTAINMENT, UK WORLD SPORTS, UK UFC/BOXING PPV, NL ODIDO VERMAAK ᴴᴰ ᴳᴼᴸᴰ, UK SKY SPORTS RED BUTTON, and 28266 VOD Directories, VOD exemple: EN 4K MAFIA & GANGSTER, EN SCHOOLING KIDS, EN WAR, EN DRAMA, NL OVERIGE, NETFLIX KIDS, EN GANGSTER & MAFIA, NL VIDEOLAND, EN RATED R, NL NETFLIX, , IPTV subscription Expire on : 08/12/2024.
SEO Tips:
Explain the product in detail, focusing on its benefits, the IPTV provider platform name: PROTV, usability, and the available streaming content. Include the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, naturally and use LSI/NLP related keywords to the post title.
Ensure to include in the end of the body the first Call to Action NATURALLY in 1 paragraph, Encourage potential customers to take action, by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously..
Ensure to include after the end of the body the Second Call to Action NATURALLY in 1 paragraph, Encourage visitors to take action, by joining our Pintrest Page https://www.pinterest.com/IPTV_SMARTERS to get the latest trial IPTV codes.
Incorporate the primary keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, and related keywords naturally throughout the excerpt, introduction and body.
Use HTML tags for better formatting and readability, such as <div>, <b>, <i>, <ul>, <li><table>, and <p> in body.
Ensure the use of a modern and stylish HTML design in the body display by using inline style = '',
Highlight the body into a stylish and modern <div>,
Clearly Mention and Highlight the platform name: PROTV with stylish text decoration using inline style.
Highlight the platform name: IPTV20SAVE at checkout for 20% off with stylish text decoration using inline style.
Highlight the first CTA: by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously. with a proper anchor texts into a new line centered HTML button tag.
Highlight the second CTA: by joining our Pintrest Page https://www.pinterest.com/IPTV_SMARTERS to get the latest trial IPTV codes with a proper anchor text into a new line centered HTML button tag.
Avoid keyword stuffing; focus on creating valuable, informative content for the reader.
Make sure the product description is engaging, informative, and optimized for search engines while providing value to the reader.
Make sure to never mention the word "Call to Action" in your response.
Finally make sure that the output overall pass all AI test detection by using Humanized and natural Text writing style.```json
{
"excerpt": "Experience the ultimate entertainment with our Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free offer, featuring an array of channels and content.",
"introduction": "Dive into a world of limitless entertainment with our exclusive offer: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free. Enjoy a seamless experience of live television and on-demand content without any financial commitment.",
"head1": "Free Trial IPTV",
"body": "<div style='font-family: Arial, sans-serif; padding: 15px; background-color: #f9f9f9; border-radius: 5px;'>" +
"<p><b>Are you seeking an exceptional IPTV experience? Look no further!</b> Our Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free allows you to explore a vast world of entertainment without any upfront costs. With no strings attached, you can evaluate our service and enjoy a plethora of channels catered to your viewing preferences.</p>" +
"<h3 style='color: #2C3E50;'>Unmatched Content Library</h3>" +
"<p>Powered by the renowned <b style='text-decoration: underline;'>PROTV</b>, our IPTV subscription boasts an impressive selection of content. With <b>5599 live streaming TV categories</b> including UK News, UK General, UK Ligue 1 PPV, and more, there's something for everyone. Plus, explore <b>28,266 VOD directories</b> that feature everything from drama series to exclusive films, ensuring you never run out of things to watch.</p>" +
"<h4 style='color: #34495E;'>Key Features of PROTV IPTV Subscription</h4>" +
"<p>The <b style='text-decoration: underline;'>PROTV</b> platform provides you with:</p>" +
"<ul style='margin-left: 20px;'>" +
"<li>Live sports coverage including UK ESPN PPV and UK UFC/Boxing PPV.</li>" +
"<li>A broad range of entertainment channels, including UK Entertainment HEVC and NL HBO Max PPV.</li>" +
"<li>Documentaries and exclusive content tailored for every interest.</li>" +
"<li>Continuous access to VOD with titles from various genres including EN 4K Mafia & Gangster and Netflix Kids.</li>" +
"</ul>" +
"<p>Subscribing to our service is as easy as entering your details, and your <b>Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free</b> starts immediately!</p>" +
"<p>Don’t let this opportunity pass you by! Seize the chance to experience unmatched entertainment options by using the discount code <span style='color: #E74C3C; font-weight: bold;'>IPTV20SAVE</span> at checkout for 20% off when purchasing our premium membership at <a href='https://Iptv2live.com/premium-membership' style='color: #2980B9; text-decoration: underline;'>https://Iptv2live.com/premium-membership</a> and unlock all premium IPTV subscriptions simultaneously.</p>" +
"<p>If you're excited to keep up-to-date with the latest trial IPTV codes, ensure you <a href='https://www.pinterest.com/IPTV_SMARTERS' style='color: #2980B9; text-decoration: underline;'>join our Pinterest Page</a> for instant updates and offers that you don't want to miss!</p>" +
"</div>"
}
```fcf2e2cf64bdcf9bb70a64f3Context: making a product page in Iptv2live.com website! that sell IPTV subscriptions from differents IPTV Platforms.
Task: Write a compelling product description for an IPTV offer with title: mac portal with access to SU SURINAME cable channels, using best SEO practices for 2024. Follow the structure outlined below and ensure the description is optimized for search engines to help it rank highly on Google. The output must be in the following JSON format:
{
"excerpt": "A concise summary mentioning the main keywords of the post title: mac portal with access to SU SURINAME cable channels.",
"introduction": "Introduction (1-2 sentences): Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: mac portal with access to SU SURINAME cable channels.",
"head1": "Shorten my title:' mac portal with access to SU SURINAME cable channels' using semantic keywords".
"body": "5 paragraphs <p></p>"
}
Structure:
excerpt: A concise summary mentioning the main keywords of the post title: mac portal with access to SU SURINAME cable channels.
introduction: Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: mac portal with access to SU SURINAME cable channels.
head1: Shorten my title:' mac portal with access to SU SURINAME cable channels' to 3 words max using semantic keywords.
body:
ensure to make the output in 5 paragraphs <p></p> with one h3 and one h4 that includes concise Description of the Key Features of the IPTV subscription from the famous IPTV provider platform named: <b>23232</b>. which Includes: 33537 cable channels categories, like: DE SKY MAX PPV 4K, UK SCOTTISH FOOTBALL, NZ NEW ZEALAND, ESTONIA, AU ESPN PLAY PPV, TR MAC KANALLARI, GR ΓΕΝΙΚΆ/GENERAL, RO CANALE DE CINEMA, PL VIAPLAY, SU SURINAME, AR KUWAIT, US CW/MY NETWORK, LAT ECUADOR, PL REGIONALNE, ICELAND SIMINN PPV, NORWAY TV2 PLAY, FR DOCUMENTAIRE HD, US NEWS NETWORK, CA ENGLISH, AR BEIN SPORTS ᵁᴴᴰ, and 156341 VOD Directories, VOD exemple: FR BIBLIQUE, AR افلام جاكي شان, AR أفلام اطفال, DK DANSKE 19002000, TR YERLI, IN TELEGU MOVIES, EN WAR, NL APPLE TV, SCA NORDIC CLASSIC, EN UK MOVIES, , IPTV subscription Expire on : 08/04/2025.
SEO Tips:
Explain the product in detail, focusing on its benefits, the IPTV provider platform name: 23232, usability, and the available streaming content. Include the keyword: mac portal with access to SU SURINAME cable channels, naturally and use LSI/NLP related keywords to the post title.
Ensure to include in the end of the body the first Call to Action NATURALLY in 1 paragraph, Encourage potential customers to take action, by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously..
Ensure to include after the end of the body the Second Call to Action NATURALLY in 1 paragraph, Encourage visitors to take action, by joining our facebook group : Free IPTV Codes https://iptv2live.com/fb.php to get the latest trial IPTV codes.
Incorporate the primary keyword: mac portal with access to SU SURINAME cable channels, and related keywords naturally throughout the excerpt, introduction and body.
Use HTML tags for better formatting and readability, such as <div>, <b>, <i>, <ul>, <li><table>, and <p> in body.
Ensure the use of a modern and stylish HTML design in the body display by using inline style = '',
Highlight the body into a stylish and modern <div>,
Clearly Mention and Highlight the platform name: 23232 with stylish text decoration using inline style.
Highlight the platform name: IPTV20SAVE at checkout for 20% off with stylish text decoration using inline style.
Highlight the first CTA: by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously. with a proper anchor texts into a new line centered HTML button tag.
Highlight the second CTA: by joining our facebook group : Free IPTV Codes https://iptv2live.com/fb.php to get the latest trial IPTV codes with a proper anchor text into a new line centered HTML button tag.
Avoid keyword stuffing; focus on creating valuable, informative content for the reader.
Make sure the product description is engaging, informative, and optimized for search engines while providing value to the reader.
Make sure to never mention the word "Call to Action" in your response.
Finally make sure that the output overall pass all AI test detection by using Humanized and natural Text writing style.```json
{
"excerpt": "Explore the mac portal with access to SU SURINAME cable channels, offering diverse entertainment through a premium IPTV subscription.",
"introduction": "Experience seamless entertainment with the mac portal with access to SU SURINAME cable channels. This IPTV subscription not only offers a wide array of cable channels but also brings the vibrant culture of Suriname right to your screen.",
"head1": "Suriname Cable Access",
"body": "<div style='font-family: Arial, sans-serif; color: #333; line-height: 1.6;'><p>With the mac portal, you gain access to a remarkable selection of SU SURINAME cable channels, ensuring you never miss out on your favorite shows and live sports events. This subscription is perfect for both local residents and expatriates, allowing you to stay connected with Surinamese culture while enjoying high-quality streaming. </p><h3 style='color: #007BFF;'>Key Features of the IPTV Subscription from <b style='text-decoration: underline;'>23232</b></h3><p>The IPTV plan from <b style='text-decoration: underline;'>23232</b> offers an impressive lineup of <b>33537 cable channels</b> and <b>156341 VOD directories</b>. Whether you're a fan of sports, movies, documentaries, or children’s content, this subscription has everything you need. Access popular categories like DE SKY MAX PPV 4K, UK SCOTTISH FOOTBALL, AU ESPN PLAY PPV, and many more, ensuring entertainment for every taste and preference.</p><h4 style='color: #007BFF;'>VOD Directory Highlights</h4><p>Dive into a plethora of options with our extensive VOD directory, featuring examples like FR BIBLIQUE, AR افلام جاكي شان, IN TELEGU MOVIES, and EN UK MOVIES. No matter what you're in the mood for, you're bound to find something captivating within our vast collection. This comprehensive selection ensures that countless hours of viewing pleasure are just a click away.</p><p>Your IPTV subscription will remain active until <b>08/04/2025</b>, providing you with long-term access to your favorite content. Don’t miss the opportunity to engage with top-tier channels and seek out exciting new titles from a variety of genres.</p><p>Now is the perfect time to act! Use the discount code <b style='text-decoration: underline;'>IPTV20SAVE</b> at checkout for <b style='text-decoration: underline;'>20% off</b> when purchasing our premium membership at <a href='https://Iptv2live.com/premium-membership' style='color: #007BFF;'>https://Iptv2live.com/premium-membership</a> and unlock all premium IPTV subscriptions simultaneously.</p><p>Additionally, stay updated with the latest offerings and get access to trial codes by joining our Facebook group: <a href='https://iptv2live.com/fb.php' style='color: #007BFF;'>Free IPTV Codes</a>. It’s your gateway to explore and enjoy new IPTV options!</p></div>"
}
```42af31daff4af19b13b9a742using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Rust;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Oxide.Plugins
{
[Info("Dungeons", "Marte6", "1.0.0")]
public class Dungeons : RustPlugin
{
private ConfigData _configData;
private DynamicConfigFile _dataFile;
private DungeonData _dungeonData;
private Timer _autoSpawnTimer;
private Timer _dungeonCheckTimer;
private Timer _updateMarkersTimer;
private Dictionary<DungeonTierConfig, string> _tierNameMap;
private const ulong OwnerID = 1020304050;
#region Hooks
private void OnServerInitialized()
{
InitializeTierNameMap();
LoadData();
RemoveAllDungeons();
StartTimerAutoSpawnDungeons();
StartTimerRemoveInactiveDungeons();
StartTimerUpdateMarkers();
SaveData();
}
private void OnServerSave() => SaveData();
private void Unload()
{
RemoveAllDungeons();
_autoSpawnTimer?.Destroy();
_dungeonCheckTimer?.Destroy();
_updateMarkersTimer?.Destroy();
SaveData();
}
object OnTurretTarget(AutoTurret turret, BaseCombatEntity target)
{
if (IsDungeonEntity(target))
{
return false;
}
return null;
}
object OnEntityTakeDamage(ScientistNPC scientistNPC, HitInfo info)
{
if (scientistNPC == null || info == null || info.Initiator == null)
return null;
AutoTurret autoTurret = info.Initiator as AutoTurret;
if (autoTurret == null)
return null;
if (IsDungeonEntity(autoTurret))
return true;
return null;
}
object CanEntityTakeDamage(AutoTurret autoTurret, HitInfo hitinfo)
{
if (autoTurret == null || hitinfo == null)
return null;
if (IsDungeonEntity(autoTurret))
{
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
else
return true;
}
return null;
}
object CanEntityTakeDamage(StorageContainer storageContainer, HitInfo hitinfo)
{
if (storageContainer == null || hitinfo == null)
return null;
if (IsDungeonEntity(storageContainer))
{
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
else
return true;
}
return null;
}
#endregion
#region Chat Commands
[ChatCommand("dun")]
private void CreateDungeonCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
AutoSpawnDungeon(player);
}
[ChatCommand("RemoveInactiveDungeons")]
private void RemoveInactiveDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
var removedDungeons = RemoveInactiveDungeons();
NotifyPlayerOnRemoval(player, removedDungeons);
}
[ChatCommand("RemoveAllDungeons")]
private void RemoveAllDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
var removedDungeons = RemoveAllDungeons();
NotifyPlayerOnRemoval(player, removedDungeons);
}
[ChatCommand("ForceRemoveAllDungeons")]
private void ForceRemoveAllDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
CleanupExistingEntities();
player.ChatMessage("All entities related to this plugin have been cleaned up.");
}
#endregion
#region Dungeon Management
private void StartTimerAutoSpawnDungeons()
{
if (_configData.DungeonSpawn.EnableAutoSpawn)
{
_autoSpawnTimer = timer.Every(
_configData.DungeonSpawn.AutoSpawnCycleInterval,
() =>
{
AutoSpawnDungeon();
}
);
}
}
private void StartTimerRemoveInactiveDungeons()
{
_dungeonCheckTimer = timer.Every(
60f,
() =>
{
var removedDungeons = RemoveInactiveDungeons();
if (removedDungeons.Count > 0)
{
foreach (var dungeon in removedDungeons)
{
LogDebug($"Removed inactive dungeon of tier {dungeon.TierName} at grid {dungeon.Grid}.");
}
}
}
);
}
private void StartTimerUpdateMarkers()
{
_updateMarkersTimer = timer.Every(30f, UpdateMarkers);
}
private void UpdateMarkers()
{
var mapMarkers = UnityEngine.Object.FindObjectsOfType<MapMarkerGenericRadius>();
foreach (var marker in mapMarkers.Where(marker => marker.IsValid()))
{
marker.SendUpdate();
}
}
private void AutoSpawnDungeon(BasePlayer player = null)
{
if (_dungeonData.ActiveDungeons.Count >= _configData.DungeonSpawn.MaxTotalActiveDungeons)
{
LogDebug(lang.GetMessage("MaxActiveDungeonsReached", this));
player?.ChatMessage(Msg(player.UserIDString, "MaxActiveDungeonsReached"));
}
else if (TryFindDungeonSpawnPoint(out var position, out var rotation))
{
LogDebug(lang.GetMessage("AttemptSpawnDungeons", this));
player?.ChatMessage(Msg(player.UserIDString, "AttemptSpawnDungeons"));
CreateDungeon(position, rotation, player);
}
else
{
LogDebug(lang.GetMessage("DungeonSpawnFailed", this));
player?.ChatMessage(Msg(player.UserIDString, "DungeonSpawnFailed"));
}
}
private void CreateDungeon(Vector3 position, Quaternion rotation, BasePlayer player = null)
{
string prefabPath = GetRandomDungeonPrefab();
if (!ValidatePrefabPath(prefabPath))
return;
if (!(GameManager.server.CreateEntity(prefabPath, position, rotation) is BasePortal dungeonPortal))
{
LogDebug("Failed to create dungeon portal.");
return;
}
var grid = PhoneController.PositionToGridCoord(position);
InitializeDungeonPortal(dungeonPortal, position, grid);
var proceduralDungeon = GetProceduralDungeon(dungeonPortal);
if (!ValidateProceduralDungeon(proceduralDungeon, dungeonPortal, grid))
return;
var selectedTier = GetDungeonTier(proceduralDungeon.spawnedCells.Count);
LogDebug($"Selected dungeon tier: {selectedTier} at grid: {grid}.");
var activeDungeon = RegisterActiveDungeon(dungeonPortal, proceduralDungeon, position, grid, selectedTier);
LogDebug($"Creating dungeon with ID: {activeDungeon.PortalId} at grid {grid}.");
ServerMgr.Instance.StartCoroutine(PopulateDungeonCoroutine(proceduralDungeon, activeDungeon, player));
}
private bool ValidatePrefabPath(string prefabPath)
{
if (!string.IsNullOrEmpty(prefabPath))
return true;
LogDebug("No prefab path available for dungeon spawning.");
return false;
}
private void InitializeDungeonPortal(BasePortal dungeonPortal, Vector3 position, string grid)
{
dungeonPortal.OwnerID = OwnerID;
dungeonPortal.Spawn();
LogDebug($"Trying to spawn Dungeon at grid: {grid}.");
}
private bool ValidateProceduralDungeon(ProceduralDynamicDungeon proceduralDungeon, BasePortal dungeonPortal, string grid)
{
if (proceduralDungeon == null)
{
LogDebug($"ProceduralDynamicDungeon not found, destroying the Dungeon at grid: {grid}.");
dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
return false;
}
if (proceduralDungeon.spawnedCells.Count < 1)
{
LogDebug($"No cells found, destroying the Dungeon at grid: {grid}.");
dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
return false;
}
return true;
}
private ActiveDungeon RegisterActiveDungeon(BasePortal dungeonPortal, ProceduralDynamicDungeon proceduralDungeon, Vector3 position, string grid, DungeonTierConfig selectedTier)
{
var activeDungeon = new ActiveDungeon
{
PortalId = dungeonPortal.net.ID.Value,
DynamicDungeonId = proceduralDungeon.net.ID.Value,
Position = position,
Grid = grid,
TierName = _tierNameMap[selectedTier],
TierConfig = selectedTier,
EntityIds = new List<ulong>(),
Spawned = false,
};
_dungeonData.ActiveDungeons.Add(activeDungeon);
return activeDungeon;
}
private void LogDebug(string message)
{
if (_configData.EnableDebug)
Puts(message);
}
private IEnumerator PopulateDungeonCoroutine(ProceduralDynamicDungeon proceduralDungeon, ActiveDungeon activeDungeon, BasePlayer player = null)
{
yield return new WaitForSeconds(3f);
LogDebug($"Starting to populate dungeon ID: {activeDungeon.PortalId}, Tier: {activeDungeon.TierName} at grid: {activeDungeon.Grid}.");
RemoveOriginalEntities(proceduralDungeon);
var allSpawnEntries = GenerateSpawnEntries(activeDungeon.TierConfig);
LogDebug($"Generated {allSpawnEntries.Count} spawn entries.");
Dictionary<Vector3, List<string>> occupiedPositions = new Dictionary<Vector3, List<string>>();
bool validSpawnLocationsExist = true;
foreach (var entry in allSpawnEntries)
{
var validLocations = GetAvailableSpawnLocations(proceduralDungeon, entry.prefab.resourcePath);
if (validLocations.Count == 0)
{
LogDebug($"No valid spawn locations found for prefab: {entry.prefab.resourcePath}");
validSpawnLocationsExist = false;
break;
}
}
if (!validSpawnLocationsExist)
{
player?.ChatMessage("No valid spawn locations available for some or all prefabs. Cancelling spawn.");
LogDebug("No valid spawn locations available for some or all prefabs. Cancelling spawn.");
yield break;
}
foreach (var entry in allSpawnEntries)
{
var validLocations = GetAvailableSpawnLocations(proceduralDungeon, entry.prefab.resourcePath);
string entityType =
entry.prefab.resourcePath.Contains("autoturret") ? "AutoTurret"
: entry.prefab.resourcePath.Contains("woodenbox") ? "StorageContainer"
: entry.prefab.resourcePath.Contains("scientist") ? "NPC"
: "Other";
bool placed = false;
var freeLocations = validLocations.Where(loc => !occupiedPositions.ContainsKey(loc.Item1)).ToList();
var locationsToTry = freeLocations.Any() ? freeLocations : validLocations;
foreach (var (position, rotation) in locationsToTry)
{
if (occupiedPositions.TryGetValue(position, out var existingTypes))
{
if (existingTypes.Contains(entityType))
{
LogDebug($"Conflict: Cannot place another {entityType} at {position}. Trying another position...");
continue;
}
if ((entityType == "AutoTurret" && existingTypes.Contains("StorageContainer")) || (entityType == "StorageContainer" && existingTypes.Contains("AutoTurret")))
{
LogDebug($"Conflict: Cannot place {entityType} at {position} because {string.Join(", ", existingTypes)} already present. Trying another position...");
continue;
}
}
var entity = GameManager.server.CreateEntity(entry.prefab.resourcePath, position, rotation);
if (entity != null)
{
entity.OwnerID = OwnerID;
entity.Spawn();
InitializeEntity(entity, activeDungeon);
if (!occupiedPositions.ContainsKey(position))
{
occupiedPositions[position] = new List<string>();
}
occupiedPositions[position].Add(entityType);
yield return new WaitForSeconds(0.5f);
if (entity.IsDestroyed || !entity.isSpawned)
{
LogDebug($"Entity {entry.prefab.resourcePath} was destroyed after spawn at {position}.");
}
else
{
LogDebug($"Placed entity {entity.ShortPrefabName} at {position}.");
}
placed = true;
break;
}
}
if (!placed)
{
var (fallbackPosition, fallbackRotation) = validLocations[Random.Range(0, validLocations.Count)];
LogDebug($"Fallback: Placing {entityType} at {fallbackPosition} despite conflicts.");
var entity = GameManager.server.CreateEntity(entry.prefab.resourcePath, fallbackPosition, fallbackRotation);
if (entity != null)
{
entity.OwnerID = OwnerID;
entity.Spawn();
InitializeEntity(entity, activeDungeon);
if (!occupiedPositions.ContainsKey(fallbackPosition))
{
occupiedPositions[fallbackPosition] = new List<string>();
}
occupiedPositions[fallbackPosition].Add(entityType);
yield return new WaitForSeconds(0.5f);
if (entity.IsDestroyed || !entity.isSpawned)
{
LogDebug($"Entity {entry.prefab.resourcePath} was destroyed after spawn at {fallbackPosition}.");
}
else
{
LogDebug($"Placed entity {entity.ShortPrefabName} at {fallbackPosition} as a fallback.");
}
}
}
}
LogDebug($"Finished placing entities. Total placed: {activeDungeon.EntityIds.Count}/{allSpawnEntries.Count}.");
CreateDungeonMarkers(activeDungeon);
NotifyPlayersOfNewDungeon(activeDungeon);
LogDebug($"Dungeon spawned successfully at grid: {activeDungeon.Grid}.");
activeDungeon.Spawned = true;
player?.ChatMessage(Msg(player.UserIDString, "DungeonSpawnedSuccess", activeDungeon.Grid));
}
private List<(Vector3 Position, Quaternion Rotation)> GetAvailableSpawnLocations(ProceduralDynamicDungeon proceduralDungeon, string prefabPath)
{
GameObject prefab = GameManager.server.FindPrefab(prefabPath);
if (prefab == null)
{
Puts($"Prefab not found: {prefabPath}");
return new List<(Vector3, Quaternion)>();
}
BaseEntity prefabEntity = prefab.GetComponent<BaseEntity>();
if (prefabEntity == null)
{
Puts($"Prefab does not have a BaseEntity: {prefabPath}");
return new List<(Vector3, Quaternion)>();
}
return proceduralDungeon
.spawnedCells.Where(cell => cell != null)
.SelectMany(cell => cell.spawnGroups)
.Where(group => group != null)
.SelectMany(group => group.spawnPoints)
.Where(spawnPoint => spawnPoint != null && spawnPoint.IsAvailableTo(prefab))
.Select(spawnPoint =>
{
spawnPoint.GetLocation(out var position, out var rotation);
return (position, rotation);
})
.ToList();
}
private void InitializeEntity(BaseEntity entity, ActiveDungeon activeDungeon)
{
if (entity != null)
{
activeDungeon.EntityIds.Add(entity.net.ID.Value);
if (entity is NPCPlayer)
{
activeDungeon.NpcIds.Add(entity.net.ID.Value);
}
else if (entity is AutoTurret turret)
{
ConfigureTurret(turret, activeDungeon.TierConfig.AutoTurretConfig);
}
else if (entity is StorageContainer storage)
{
ConfigureStorage(storage);
}
}
}
private void RemoveOriginalEntities(ProceduralDynamicDungeon proceduralDungeon)
{
foreach (var cell in proceduralDungeon.spawnedCells)
{
foreach (var group in cell.spawnGroups)
{
for (int i = group.spawnInstances.Count - 1; i >= 0; i--)
{
SpawnPointInstance spawnPointInstance = group.spawnInstances[i];
BaseEntity entity = spawnPointInstance?.gameObject?.ToBaseEntity();
if (entity != null && !IsDungeonEntity(entity) && (entity is NPCDwelling || entity is ScarecrowNPC || entity is LootContainer))
{
LogDebug($"Removing original entity: {entity.ShortPrefabName} at {entity.transform.position}");
entity.Kill();
}
}
}
}
}
private List<SpawnGroup.SpawnEntry> GenerateSpawnEntries(DungeonTierConfig tierConfig)
{
var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();
AddNpcEntries(tierConfig, allSpawnEntries);
AddTurretEntries(tierConfig, allSpawnEntries);
AddLootEntries(tierConfig, allSpawnEntries);
return allSpawnEntries.OrderBy(_ => Random.value).ToList();
}
private void AddNpcEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
{
foreach (var config in tierConfig.NpcSpawnConfigs)
{
if (GameManifest.pathToGuid.TryGetValue(config.PrefabName, out var guid))
{
spawnEntries.AddRange(
Enumerable.Repeat(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = guid },
weight = 1,
mobile = true,
},
config.Total
)
);
}
}
}
private void AddTurretEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
{
var prefabName = "assets/prefabs/npc/autoturret/autoturret_deployed.prefab";
if (GameManifest.pathToGuid.TryGetValue(prefabName, out var turretGuid))
{
spawnEntries.AddRange(
Enumerable.Repeat(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = turretGuid },
weight = 1,
mobile = false,
},
tierConfig.AutoTurretConfig.Total
)
);
}
}
private void AddLootEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
{
var prefabName = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab";
if (GameManifest.pathToGuid.TryGetValue(prefabName, out var boxGuid))
{
spawnEntries.AddRange(
Enumerable.Repeat(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = boxGuid },
weight = 1,
mobile = false,
},
tierConfig.TotalLootBoxes
)
);
}
}
private void ConfigureTurret(AutoTurret turret, TurretConfig turretConfig)
{
turret.InitializeHealth(turretConfig.Health, turretConfig.Health);
var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
weapon?.MoveToContainer(turret.inventory, 0);
var laserSight = ItemManager.CreateByName("weapon.mod.lasersight");
laserSight?.MoveToContainer(weapon.contents);
turret.UpdateAttachedWeapon();
turret.gameObject.AddComponent<TurretMonoBehaviour>();
}
public class TurretMonoBehaviour : MonoBehaviour
{
private AutoTurret _turret;
private void Awake()
{
_turret = GetComponent<AutoTurret>();
_turret.SetPeacekeepermode(false);
_turret.InitiateStartup();
_turret.isLootable = false;
_turret.dropFloats = false;
_turret.dropsLoot = false;
InvokeRepeating(nameof(RefillAmmo), 2f, 30f);
}
private void RefillAmmo()
{
int maxReserveAmmo = 1000;
if (_turret.AttachedWeapon is not BaseProjectile baseProjectile || baseProjectile.primaryMagazine?.ammoType == null)
return;
var ammoType = baseProjectile.primaryMagazine.ammoType;
int currentAmmoCount = _turret.inventory.itemList.Where(item => item.info == ammoType).Sum(item => item.amount);
if (currentAmmoCount < maxReserveAmmo)
{
int ammoNeeded = maxReserveAmmo - currentAmmoCount;
Item ammoItem = ItemManager.Create(ammoType, ammoNeeded);
ammoItem?.MoveToContainer(_turret.inventory);
_turret.UpdateTotalAmmo();
_turret.EnsureReloaded();
_turret.SendNetworkUpdateImmediate();
}
}
private void OnDestroy()
{
CancelInvoke(nameof(RefillAmmo));
}
}
private void ConfigureStorage(StorageContainer storage)
{
storage.skinID = _configData.LootBoxConfig.SmallWoodBoxSkinID;
AddCodeLock(storage);
PopulateStorage(storage.inventory, _configData.LootBoxConfig.LootItems);
}
private void AddCodeLock(StorageContainer storage)
{
var prefabName = "assets/prefabs/locks/keypad/lock.code.prefab";
var codeLock = GameManager.server.CreateEntity(prefabName) as CodeLock;
if (codeLock == null)
return;
codeLock.SetParent(storage, storage.GetSlotAnchorName(BaseEntity.Slot.Lock));
codeLock.Spawn();
codeLock.code = Random.Range(1000, 9999).ToString();
codeLock.hasCode = true;
codeLock.SetFlag(BaseEntity.Flags.Locked, true);
}
private void PopulateStorage(ItemContainer container, List<ItemConfig> lootItems)
{
var shuffledItems = lootItems.OrderBy(_ => Random.value).ToList();
var differentItemsCount = 0;
foreach (var itemConfig in shuffledItems)
{
if (differentItemsCount >= _configData.LootBoxConfig.MaxDifferentItemsPerBox)
{
break;
}
if (Random.value * 100f <= itemConfig.InclusionChancePercentage)
{
var itemDefinition = ItemManager.FindItemDefinition(itemConfig.ShortName);
if (itemDefinition != null)
{
var amount = Random.Range(itemConfig.MinimumAmount, itemConfig.MaximumAmount + 1);
if (container.itemList.Count < container.capacity)
{
var item = ItemManager.Create(itemDefinition, amount);
item.MoveToContainer(container);
differentItemsCount++;
}
}
}
}
}
private List<ActiveDungeon> RemoveInactiveDungeons()
{
var removedDungeons = new List<ActiveDungeon>();
foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
{
if (CanBeRemoved(dungeon))
{
removedDungeons.Add(dungeon);
DestroyDungeon(dungeon);
}
}
SaveData();
return removedDungeons;
}
private void DestroyDungeon(ActiveDungeon dungeon)
{
var dungeonEntity = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.PortalId)) as HalloweenDungeon;
if (dungeonEntity != null)
{
dungeonEntity?.Kill(BaseNetworkable.DestroyMode.None);
}
var proceduralDynamicDungeon = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.DynamicDungeonId)) as ProceduralDynamicDungeon;
if (proceduralDynamicDungeon != null)
{
RemoveLootableCorpse(proceduralDynamicDungeon);
RemoveItemDrop(proceduralDynamicDungeon);
RemoveDroppedItemContainer(proceduralDynamicDungeon);
}
DestroyEntities(dungeon);
RemoveDungeonMarkers(dungeon);
_dungeonData.ActiveDungeons.Remove(dungeon);
}
private void RemoveDroppedItemContainer(ProceduralDynamicDungeon dungeon)
{
List<DroppedItemContainer> obj = Pool.Get<List<DroppedItemContainer>>();
Vis.Entities(dungeon.transform.position, 80f, obj);
foreach (DroppedItemContainer item in obj)
{
if (item.IsValid() && !item.IsDestroyed)
{
item?.Kill();
}
}
Pool.FreeUnmanaged(ref obj);
}
private void RemoveItemDrop(ProceduralDynamicDungeon dungeon)
{
List<DroppedItem> obj = Pool.Get<List<DroppedItem>>();
Vis.Entities(dungeon.transform.position, 80f, obj);
foreach (DroppedItem item in obj)
{
if (item.IsValid() && !item.IsDestroyed)
{
item?.Kill();
}
}
Pool.FreeUnmanaged(ref obj);
}
private void RemoveLootableCorpse(ProceduralDynamicDungeon dungeon)
{
List<LootableCorpse> obj = Pool.Get<List<LootableCorpse>>();
Vis.Entities(dungeon.transform.position, 80f, obj);
foreach (LootableCorpse item in obj)
{
if (item.IsValid() && !item.IsDestroyed)
{
item?.Kill();
}
}
Pool.FreeUnmanaged(ref obj);
}
private void DestroyEntities(ActiveDungeon dungeon)
{
foreach (var entityId in dungeon.EntityIds)
{
var entity = BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as BaseEntity;
entity?.Kill(BaseNetworkable.DestroyMode.None);
}
dungeon.EntityIds.Clear();
}
private void RemoveDungeonMarkers(ActiveDungeon dungeon)
{
foreach (var marker in _dungeonData.ActiveDungeonMarkers.Where(m => m.Position == dungeon.Position).ToList())
{
var vendingMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.VendingMarker)) as BaseEntity;
var radiusMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.RadiusMarker)) as BaseEntity;
vendingMarker?.Kill(BaseNetworkable.DestroyMode.None);
radiusMarker?.Kill(BaseNetworkable.DestroyMode.None);
_dungeonData.ActiveDungeonMarkers.Remove(marker);
}
}
private IEnumerable<ActiveDungeon> RemoveAllDungeons()
{
var removedDungeons = new List<ActiveDungeon>(_dungeonData.ActiveDungeons);
foreach (var dungeon in removedDungeons)
{
DestroyDungeon(dungeon);
}
_dungeonData = new DungeonData();
SaveData();
return removedDungeons;
}
#endregion
#region Utility Methods
private ProceduralDynamicDungeon GetProceduralDungeon(BasePortal dungeonPortal)
{
return (dungeonPortal as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeonPortal as HalloweenDungeon)?.dungeonInstance.Get(true);
}
private void CleanupExistingEntities()
{
foreach (
var entityType in new Type[]
{
typeof(VendingMachineMapMarker),
typeof(MapMarkerGenericRadius),
typeof(BasePortal),
typeof(ProceduralDynamicDungeon),
typeof(AutoTurret),
typeof(NPCPlayer),
typeof(StorageContainer),
}
)
{
foreach (var obj in UnityEngine.Object.FindObjectsOfType(entityType))
{
if (obj is BaseEntity baseEntity && baseEntity.OwnerID == OwnerID)
{
baseEntity.Kill(BaseNetworkable.DestroyMode.None);
}
}
}
_dungeonData = new DungeonData();
}
private void InitializeTierNameMap()
{
_tierNameMap = new Dictionary<DungeonTierConfig, string>
{
{ _configData.Tiers.Easy, "Easy" },
{ _configData.Tiers.Normal, "Normal" },
{ _configData.Tiers.Medium, "Medium" },
{ _configData.Tiers.Hard, "Hard" },
{ _configData.Tiers.Nightmare, "Nightmare" },
};
}
private void LoadData()
{
_dataFile = Interface.Oxide.DataFileSystem.GetFile("Dungeons_Data");
_dungeonData = _dataFile.ReadObject<DungeonData>() ?? new DungeonData();
}
private void SaveData()
{
_dataFile.WriteObject(_dungeonData);
}
private static bool IsPlayer(BasePlayer targetPlayer) => targetPlayer != null && targetPlayer.userID.IsSteamId();
private void NotifyPlayersOfNewDungeon(ActiveDungeon activeDungeon)
{
foreach (var player in BasePlayer.activePlayerList)
{
var message = Msg(player.UserIDString, "DungeonAppeared", activeDungeon.TierName, activeDungeon.Grid);
player.ChatMessage(message);
player.ShowToast(GameTip.Styles.Blue_Normal, message);
}
}
private void NotifyPlayerOnRemoval(BasePlayer player, IEnumerable<ActiveDungeon> removedDungeons)
{
if (!removedDungeons.Any())
{
player.ChatMessage(Msg(player.UserIDString, "NoInactiveDungeonsToRemove"));
return;
}
foreach (var dungeon in removedDungeons)
{
player.ChatMessage(Msg(player.UserIDString, "DungeonRemovedDetail", dungeon.TierName, dungeon.Grid));
}
}
private bool IsAdmin(BasePlayer player) => player != null && player.IsAdmin;
private bool IsDungeonEntity(BaseEntity entity) => entity != null && _dungeonData.ActiveDungeons.Any(d => d.EntityIds.Contains(entity.net.ID.Value));
private string GetRandomDungeonPrefab()
{
var prefabs = new List<string>();
if (_configData.DungeonSpawn.EnableXmasDungeon)
prefabs.Add("assets/prefabs/missions/portal/xmasportalentry.prefab");
if (_configData.DungeonSpawn.EnableHalloweenDungeon)
prefabs.Add("assets/prefabs/missions/portal/halloweenportalentry.prefab");
return prefabs.Count > 0 ? prefabs[Random.Range(0, prefabs.Count)] : null;
}
private static bool IsValidPosition(Vector3 position, out Vector3 suitablePosition, out Quaternion suitableRotation)
{
suitablePosition = Vector3.zero;
suitableRotation = Quaternion.identity;
if (!LocationCheck.GetTerrainInfo(position, out var hitInfo))
return false;
if (LocationCheck.InsideRock(position, 10f))
return false;
if (LocationCheck.InWater(position))
return false;
if (LocationCheck.OnRoadOrRail(position))
return false;
if (LocationCheck.InNoBuildZone(position, 10f))
return false;
return SetSuitablePositionAndRotation(position, hitInfo, out suitablePosition, out suitableRotation);
}
private static bool SetSuitablePositionAndRotation(Vector3 position, RaycastHit hitInfo, out Vector3 suitablePosition, out Quaternion suitableRotation)
{
suitablePosition = hitInfo.point;
suitableRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
return true;
}
private void CreateDungeonMarkers(ActiveDungeon activeDungeon)
{
var prefabVendingMarker = "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab";
var vendingMarker = CreateMapMarker<VendingMachineMapMarker>(prefabVendingMarker, activeDungeon.Position);
vendingMarker.OwnerID = OwnerID;
vendingMarker.markerShopName = $"Dungeon: {activeDungeon.TierName}";
vendingMarker.SendNetworkUpdate();
var prefabRadiusMarker = "assets/prefabs/tools/map/genericradiusmarker.prefab";
var radiusMarker = CreateMapMarker<MapMarkerGenericRadius>(prefabRadiusMarker, activeDungeon.Position);
radiusMarker.OwnerID = OwnerID;
radiusMarker.alpha = 0.75f;
radiusMarker.radius = 0.5f;
radiusMarker.color2 = GetMarkerColorByTier(activeDungeon.TierName);
radiusMarker.SendUpdate();
radiusMarker.SendNetworkUpdate();
_dungeonData.ActiveDungeonMarkers.Add(
new DungeonMarker
{
VendingMarker = vendingMarker.net.ID.Value,
RadiusMarker = radiusMarker.net.ID.Value,
Position = activeDungeon.Position,
Tier = activeDungeon.TierName,
}
);
}
private T CreateMapMarker<T>(string prefab, Vector3 position)
where T : BaseEntity
{
var entity = GameManager.server.CreateEntity(prefab, position) as T;
entity?.Spawn();
return entity;
}
private static Color GetMarkerColorByTier(string tierName)
{
return tierName switch
{
"Easy" => Color.green,
"Normal" or "Medium" => Color.yellow,
"Hard" => Color.red,
"Nightmare" => Color.black,
_ => Color.white,
};
}
private bool CanBeRemoved(ActiveDungeon dungeon)
{
return AreAllNpcsGone(dungeon) && NoPlayersInside(dungeon) && dungeon.Spawned;
}
private bool AreAllNpcsGone(ActiveDungeon dungeon)
{
return dungeon.NpcIds.All(npcId =>
{
var npc = BaseNetworkable.serverEntities.Find(new NetworkableId(npcId)) as BaseEntity;
return npc == null || npc.IsDestroyed;
});
}
private bool NoPlayersInside(ActiveDungeon dungeon)
{
var dynamicDungeon = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.DynamicDungeonId)) as ProceduralDynamicDungeon;
return dynamicDungeon == null || !dynamicDungeon.ContainsAnyPlayers();
}
private bool TryFindDungeonSpawnPoint(out Vector3 position, out Quaternion rotation)
{
var mapSize = World.Size;
for (var attempt = 0; attempt < 1000; attempt++)
{
var candidatePosition = new Vector3(Random.Range(-mapSize / 2, mapSize / 2), 0, Random.Range(-mapSize / 2, mapSize / 2));
if (IsValidPosition(candidatePosition, out position, out rotation) && IsSafeFromOtherDungeons(position) && !IsCloseToMonuments(position))
{
return true;
}
}
position = Vector3.zero;
rotation = Quaternion.identity;
return false;
}
private bool IsSafeFromOtherDungeons(Vector3 position)
{
return _dungeonData.ActiveDungeons.All(dungeon => Vector3.Distance(position, dungeon.Position) >= _configData.DungeonSpawn.MinDistanceBetweenDungeons);
}
private bool IsCloseToMonuments(Vector3 position)
{
return TerrainMeta.Path.Monuments.Any(mon => Vector3.Distance(position, mon.transform.position) < _configData.DungeonSpawn.MinDistanceFromMonuments);
}
private DungeonTierConfig GetDungeonTier(int cellCount)
{
return cellCount switch
{
< 5 => _configData.Tiers.Easy,
< 8 => _configData.Tiers.Normal,
< 12 => _configData.Tiers.Medium,
< 16 => _configData.Tiers.Hard,
_ => _configData.Tiers.Nightmare,
};
}
#endregion
#region Configuration
private class ConfigData
{
[JsonProperty(PropertyName = "Dungeon Spawn Settings")]
public DungeonSpawnSettings DungeonSpawn { get; set; } = new DungeonSpawnSettings();
[JsonProperty(PropertyName = "Tiers")]
public DungeonTiers Tiers { get; set; } = new DungeonTiers();
[JsonProperty(PropertyName = "Loot Box Config")]
public LootBoxConfig LootBoxConfig { get; set; } = new LootBoxConfig();
[JsonProperty("Enable Debug")]
public bool EnableDebug { get; set; } = true;
[JsonProperty(PropertyName = "Version")]
public VersionNumber Version { get; set; }
}
private class DungeonTiers
{
[JsonProperty(PropertyName = "Easy")]
public DungeonTierConfig Easy { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 1,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 1 },
},
AutoTurretConfig = new TurretConfig
{
Total = 1,
Health = 300,
WeaponShortName = "pistol.revolver",
},
};
[JsonProperty(PropertyName = "Normal")]
public DungeonTierConfig Normal { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 2,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 3 },
},
AutoTurretConfig = new TurretConfig
{
Total = 2,
Health = 600,
WeaponShortName = "smg.2",
},
};
[JsonProperty(PropertyName = "Medium")]
public DungeonTierConfig Medium { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 4,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 4 },
},
AutoTurretConfig = new TurretConfig
{
Total = 3,
Health = 1000,
WeaponShortName = "smg.mp5",
},
};
[JsonProperty(PropertyName = "Hard")]
public DungeonTierConfig Hard { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 5,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 4 },
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 3 },
},
AutoTurretConfig = new TurretConfig
{
Total = 4,
Health = 1500,
WeaponShortName = "rifle.ak",
},
};
[JsonProperty(PropertyName = "Nightmare")]
public DungeonTierConfig Nightmare { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 6,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 5 },
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 5 },
},
AutoTurretConfig = new TurretConfig
{
Total = 6,
Health = 2000,
WeaponShortName = "rifle.ak",
},
};
}
private class DungeonTierConfig
{
[JsonProperty(PropertyName = "Maximum Loot Boxes")]
public int TotalLootBoxes { get; set; }
[JsonProperty(PropertyName = "NPC Spawn Configs", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<NpcSpawnConfig> NpcSpawnConfigs { get; set; }
[JsonProperty(PropertyName = "Auto Turret Config")]
public TurretConfig AutoTurretConfig { get; set; } = new TurretConfig();
}
public class LootBoxConfig
{
[JsonProperty(PropertyName = "Small Wood Box Skin ID")]
public ulong SmallWoodBoxSkinID { get; set; } = 2998755525;
[JsonProperty(PropertyName = "Max Different Items Per Box")]
public int MaxDifferentItemsPerBox { get; set; } = 6;
[JsonProperty(PropertyName = "Loot Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<ItemConfig> LootItems { get; set; } =
new List<ItemConfig>
{
new ItemConfig
{
ShortName = "ammo.rifle",
InclusionChancePercentage = 15,
MinimumAmount = 100,
MaximumAmount = 300,
},
};
}
private class NpcSpawnConfig
{
[JsonProperty(PropertyName = "Prefab")]
public string PrefabName { get; set; }
[JsonProperty(PropertyName = "Maximum")]
public int Total { get; set; }
}
private class TurretConfig
{
[JsonProperty(PropertyName = "Maximum")]
public int Total { get; set; } = 3;
[JsonProperty(PropertyName = "Health")]
public float Health { get; set; } = 1000f;
[JsonProperty(PropertyName = "Weapon Short Name")]
public string WeaponShortName { get; set; } = "rifle.ak";
}
private class DungeonSpawnSettings
{
[JsonProperty(PropertyName = "Enable Auto Spawn")]
public bool EnableAutoSpawn { get; set; } = true;
[JsonProperty(PropertyName = "Auto Spawn Cycle Interval")]
public float AutoSpawnCycleInterval { get; set; } = 60f;
[JsonProperty(PropertyName = "Enable Xmas Dungeon")]
public bool EnableXmasDungeon { get; set; } = true;
[JsonProperty(PropertyName = "Enable Halloween Dungeon")]
public bool EnableHalloweenDungeon { get; set; } = true;
[JsonProperty(PropertyName = "Max Total Active Dungeons")]
public int MaxTotalActiveDungeons { get; set; } = 4;
[JsonProperty(PropertyName = "Min Distance From Monuments")]
public float MinDistanceFromMonuments { get; set; } = 100f;
[JsonProperty(PropertyName = "Min Distance Between Dungeons")]
public float MinDistanceBetweenDungeons { get; set; } = 100f;
}
public class ItemConfig
{
[JsonProperty(PropertyName = "ShortName")]
public string ShortName { get; set; }
[JsonProperty(PropertyName = "Inclusion Chance Percentage")]
public float InclusionChancePercentage { get; set; }
[JsonProperty(PropertyName = "Minimum Amount")]
public int MinimumAmount { get; set; }
[JsonProperty(PropertyName = "Maximum Amount")]
public int MaximumAmount { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_configData = Config.ReadObject<ConfigData>() ?? new ConfigData();
}
catch (Exception ex)
{
PrintError($"The configuration file is corrupt: {ex}");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
PrintWarning("Creating a new configuration file.");
_configData = new ConfigData { Version = Version };
}
protected override void SaveConfig()
{
Config.WriteObject(_configData, true);
}
#endregion
#region Localization
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(
new Dictionary<string, string>
{
["NoInactiveDungeonsToRemove"] = "There are no inactive dungeons to remove.",
["NoDungeonsToRemove"] = "There are no dungeons to remove.",
["DungeonRemovedDetail"] = "Removed dungeon of tier {0} at grid {1}.",
["AttemptSpawnDungeons"] = "Attempting to spawn dungeons.",
["DungeonAppeared"] = "A {0} Dungeon has appeared at grid {1}!",
["DungeonSpawnFailed"] = "Could not find a valid location to spawn a new dungeon.",
["DungeonSpawnedSuccess"] = "Dungeon spawned successfully at grid {0}!",
["MaxActiveDungeonsReached"] = "Cannot spawn: Maximum number of active dungeons reached.",
},
this
);
}
private string Msg(string userId, string key, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
#endregion
#region Data Classes
private class DungeonData
{
public List<ActiveDungeon> ActiveDungeons { get; set; } = new List<ActiveDungeon>();
public List<DungeonMarker> ActiveDungeonMarkers { get; set; } = new List<DungeonMarker>();
}
private class ActiveDungeon
{
public ulong PortalId { get; set; }
public ulong DynamicDungeonId { get; set; }
public Vector3 Position { get; set; }
public string Grid { get; set; }
public string TierName { get; set; }
public DungeonTierConfig TierConfig { get; set; }
public List<ulong> EntityIds { get; set; } = new List<ulong>();
public List<ulong> NpcIds { get; set; } = new List<ulong>();
public bool Spawned { get; set; } = false;
}
private class DungeonMarker
{
public ulong VendingMarker { get; set; }
public ulong RadiusMarker { get; set; }
public Vector3 Position { get; set; }
public string Tier { get; set; }
}
#endregion
#region Collider Checks
public static class LocationCheck
{
public static bool InsideRock(Vector3 position, float radius)
{
List<Collider> colliders = Pool.GetList<Collider>();
Vis.Colliders(position, radius, colliders, Layers.Mask.World, QueryTriggerInteraction.Ignore);
bool isInsideRock = colliders.Any(collider =>
collider.name.Contains("rock", StringComparison.OrdinalIgnoreCase)
|| collider.name.Contains("cliff", StringComparison.OrdinalIgnoreCase)
|| collider.name.Contains("formation", StringComparison.OrdinalIgnoreCase)
);
Pool.FreeList(ref colliders);
return isInsideRock;
}
public static bool GetTerrainInfo(Vector3 position, out RaycastHit hitInfo)
{
return Physics.Linecast(position + Vector3.up, position - Vector3.up, out hitInfo, Layers.Mask.Terrain);
}
public static bool InWater(Vector3 position) => WaterLevel.Test(position, false, false);
public static bool OnRoadOrRail(Vector3 position) => (TerrainMeta.TopologyMap.GetTopology(position) & (int)TerrainTopology.Enum.Road) != 0;
public static bool InNoBuildZone(Vector3 position, float radius) => Physics.CheckSphere(position, radius, Layers.Mask.Prevent_Building, QueryTriggerInteraction.Ignore);
}
#endregion
}
}
The `Dungeons` plugin you provided is a comprehensive implementation for managing dungeon spawn mechanics in a Rust server environment. Below, I will summarize the main components and functionalities of this plugin for better understanding:
### Overview
1. **Plugin Header and Metadata**:
- The plugin is defined with the name "Dungeons", authored by "Marte6", and has a version number (1.0.0).
2. **Timers**:
- The plugin uses multiple timers to handle automatic dungeon spawning, cleaning up inactive dungeons, and updating map markers for active dungeons.
3. **Dungeons Management**:
- Dungeons are represented by instances of `ActiveDungeon`, which hold information pertaining to each spawned dungeon (positions, entities, etc.).
- The plugin supports multiple tiers of dungeons: Easy, Normal, Medium, Hard, and Nightmare, each with its unique configuration (e.g., NPCs, turrets, loot).
4. **Spawn Logic**:
- Dungeons are spawned based on randomized positions validated by terrain checks.
- Validations include checking terrain, nearby monuments, and existing dungeons to ensure no overlap.
5. **Entity Management**:
- Entities within dungeons (like NPCs and turrets) are spawned based on defined configurations:
- NPCs are spawned per the configuration related to their tiers, equipped with specific weapons and health.
- Lootable containers are also configured; they can spawn various items based on predefined probabilities.
6. **Command Handling**:
- Several chat commands available for admins to interact with the plugin:
- `dun`: Manually create a dungeon.
- `RemoveInactiveDungeons`: Clean up inactive dungeons.
- `RemoveAllDungeons`: Remove all active dungeons.
- `ForceRemoveAllDungeons`: Force remove all dungeon-related entities.
7. **Message and Logging**:
- Uses a localization method to manage user-facing messages.
- Custom logging is implemented for debugging purposes, toggled via configuration.
8. **Collisions and Restrictions**:
- Utilizes spatial checks to determine if a proposed spawn point is within rocks, in water, or too close to roads/other structures to maintain game balance.
9. **Configuration**:
- The configuration is loaded from a JSON file, allowing dynamic adjustments without recompiling the plugin.
- Consists of spawn settings, tier configurations, and loot box settings.
### Functional Highlights
- **Timers**:
- Automatically manages dungeon spawning and cleanup without manual intervention using background timers.
- **Dungeons Creation**:
- A variety of prefabs for dungeon portals are supported, allowing flexibility in dungeon type.
- **User Feedback**:
- Players receive notifications regarding dungeon creation and removal, helping communicate changes in the game world.
- **Automated NPC Management**:
- AutoTurrets are set to refill ammo periodically without requiring user actions.
### Potential Improvements and Extensions
- **Dynamic Difficulty Adjustment**: Implement adaptive difficulty based on player activity or server events.
- **Enhanced Loot System**: More complex loot distributions or randomized event outcomes to encourage exploration.
- **Event Hooks**: Integrate with existing server events or player actions to take real-time dungeon adjustments (e.g., buffing or weakening monsters).
This plugin serves as a robust foundation for adding dungeon mechanics to a Rust server, greatly enhancing the gameplay experience by introducing new challenges and systems for players to engage with.ac52afbef62ec24f6db9c124Context: making a product page in Iptv2live.com website! that sell IPTV subscriptions from differents IPTV Platforms.
Task: Write a compelling product description for an IPTV offer with title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, using best SEO practices for 2024. Follow the structure outlined below and ensure the description is optimized for search engines to help it rank highly on Google. The output must be in the following JSON format:
{
"excerpt": "A concise summary mentioning the main keywords of the post title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.",
"introduction": "Introduction (1-2 sentences): Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.",
"head1": "Shorten my title:'Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free' using semantic keywords".
"body": "5 paragraphs <p></p>"
}
Structure:
excerpt: A concise summary mentioning the main keywords of the post title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.
introduction: Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.
head1: Shorten my title:'Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free' to 3 words max using semantic keywords.
body:
ensure to make the output in 5 paragraphs <p></p> with one h3 and one h4 that includes concise Description of the Key Features of the IPTV subscription from the famous IPTV provider platform named: <b>PROTV</b>. which Includes: 5599 live streaming TV categories, like: UK NEWS, UK GENERAL, BE DOCUMENTAIRE, UK LIGUE 1 PPV, UK PDC BOARD PPV, All, UK NOW SPORT ᴴᴰ, BE BELGIUM DAZN EXCLUSIVE ᴿᴬᵂ, NL HBO MAX PPV, NL FEYENOORD ONE PPV, UK ENTERTAINMENT HEVC, USA, UK DOCUMENTARY, UK NATIONAL LEAGUE, UK ESPN PPV, UK ENTERTAINMENT, UK WORLD SPORTS, UK UFC/BOXING PPV, NL ODIDO VERMAAK ᴴᴰ ᴳᴼᴸᴰ, UK SKY SPORTS RED BUTTON, and 28266 VOD Directories, VOD exemple: EN 4K MAFIA & GANGSTER, EN SCHOOLING KIDS, EN WAR, EN DRAMA, NL OVERIGE, NETFLIX KIDS, EN GANGSTER & MAFIA, NL VIDEOLAND, EN RATED R, NL NETFLIX, , IPTV subscription Expire on : 08/12/2024.
SEO Tips:
Explain the product in detail, focusing on its benefits, the IPTV provider platform name: PROTV, usability, and the available streaming content. Include the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, naturally and use LSI/NLP related keywords to the post title.
Ensure to include in the end of the body the first Call to Action NATURALLY in 1 paragraph, Encourage potential customers to take action, by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously..
Ensure to include after the end of the body the Second Call to Action NATURALLY in 1 paragraph, Encourage visitors to take action, by joining our Pintrest Page https://www.pinterest.com/IPTV_SMARTERS to get the latest trial IPTV codes.
Incorporate the primary keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, and related keywords naturally throughout the excerpt, introduction and body.
Use HTML tags for better formatting and readability, such as <div>, <b>, <i>, <ul>, <li><table>, and <p> in body.
Ensure the use of a modern and stylish HTML design in the body display by using inline style = '',
Highlight the body into a stylish and modern <div>,
Clearly Mention and Highlight the platform name: PROTV with stylish text decoration using inline style.
Highlight the platform name: IPTV20SAVE at checkout for 20% off with stylish text decoration using inline style.
Highlight the first CTA: by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously. with a proper anchor texts into a new line centered HTML button tag.
Highlight the second CTA: by joining our Pintrest Page https://www.pinterest.com/IPTV_SMARTERS to get the latest trial IPTV codes with a proper anchor text into a new line centered HTML button tag.
Avoid keyword stuffing; focus on creating valuable, informative content for the reader.
Make sure the product description is engaging, informative, and optimized for search engines while providing value to the reader.
Make sure to never mention the word "Call to Action" in your response.
Finally make sure that the output overall pass all AI test detection by using Humanized and natural Text writing style.```json
{
"excerpt": "Experience risk-free entertainment with our Free Test IPTV For 14 Days, starting from 23/11/2024.",
"introduction": "Discover endless entertainment with our Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free. Enjoy the best streaming content available without any obligations.",
"head1": "14-Day IPTV Trial",
"body": "<div style='font-family: Arial, sans-serif; line-height: 1.6; color: #333; padding: 20px; border: 1px solid #ccc; border-radius: 8px; background-color: #f9f9f9;'>"+
"<p>Embark on an unparalleled streaming experience with our <b style='text-decoration: underline;'>Free Test IPTV For 14 Days</b> - starting from 23/11/2024 risk-free. This exceptional offer allows you to explore premium content from the renowned IPTV provider <b style='text-decoration: underline;'>PROTV</b> without any financial risk, letting you test the platform's capabilities to meet your entertainment needs.</p>"+
"<h3 style='color: #007bff;'>Why Choose PROTV?</h3>"+
"<p>With <b style='text-decoration: underline;'>PROTV</b>, you gain access to a staggering array of 5,599 live streaming TV channels, encompassing various categories like UK NEWS, UK GENERAL, and sports PPV events. The diversity of programming ensures there is something for everyone, allowing you to enjoy your favorite shows, movies, and sporting events all in one place.</p>"+
"<h4 style='color: #007bff;'>Key Features of the PROTV IPTV Subscription:</h4>"+
"<p><ul>"+
"<li>Comprehensive programming across <i>28266 VOD directories</i> including genres such as EN DRAMA, <i>NL VIDEOLAND</i>, and <i>NETFLIX</i> selections.</li>"+
"<li>Exclusive access to UK sporting events through channels like <i>UK SKY SPORTS RED BUTTON</i>, <i>UK UFC/BOXING PPV</i>, and more.</li>"+
"<li>High-definition streaming quality ensuring an immersive viewing experience.</li>"+
"<li>Access to special events including <i>UK PDC BOARD PPV</i> and <i>NL HBO MAX PPV</i>.</li>"+
"<li>Easy setup and user-friendly interface for a seamless streaming experience.</li>"+
"</ul></p>"+
"<p>Our IPTV subscriptions will expire on <b>08/12/2024</b>, so make sure to take advantage of this offer while it lasts! By signing up for the <b style='text-decoration: underline;'>Free Test IPTV For 14 Days</b>, you can explore the extensive library and streaming options PROTV offers without paying a dime.</p>"+
"<p style='font-weight: bold; text-align: center;'>Don't miss out on this opportunity! Use the discount code <b style='text-decoration: underline;'>IPTV20SAVE</b> at checkout for 20% off when purchasing our premium membership at <a href='https://Iptv2live.com/premium-membership' style='color: #007bff;'>https://Iptv2live.com/premium-membership</a> and unlock all premium IPTV subscriptions simultaneously.</p>"+
"<p style='font-weight: bold; text-align: center;'>Join our Pinterest Page <a href='https://www.pinterest.com/IPTV_SMARTERS' style='color: #007bff;'>https://www.pinterest.com/IPTV_SMARTERS</a> to get the latest trial IPTV codes and updates!</p>"+
"</div>"
}
```7d08001644a3d16133c18791Context: making a product page in Iptv2live.com website! that sell IPTV subscriptions from differents IPTV Platforms.
Task: Write a compelling product description for an IPTV offer with title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, using best SEO practices for 2024. Follow the structure outlined below and ensure the description is optimized for search engines to help it rank highly on Google. The output must be in the following JSON format:
{
"excerpt": "A concise summary mentioning the main keywords of the post title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.",
"introduction": "Introduction (1-2 sentences): Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.",
"head1": "Shorten my title:'Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free' using semantic keywords".
"body": "5 paragraphs <p></p>"
}
Structure:
excerpt: A concise summary mentioning the main keywords of the post title: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.
introduction: Provide a brief introduction to the product, highlighting its main benefit and mentioning the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free.
head1: Shorten my title:'Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free' to 3 words max using semantic keywords.
body:
ensure to make the output in 5 paragraphs <p></p> with one h3 and one h4 that includes concise Description of the Key Features of the IPTV subscription from the famous IPTV provider platform named: <b>PROTV</b>. which Includes: 5599 live streaming TV categories, like: UK NEWS, UK GENERAL, BE DOCUMENTAIRE, UK LIGUE 1 PPV, UK PDC BOARD PPV, All, UK NOW SPORT ᴴᴰ, BE BELGIUM DAZN EXCLUSIVE ᴿᴬᵂ, NL HBO MAX PPV, NL FEYENOORD ONE PPV, UK ENTERTAINMENT HEVC, USA, UK DOCUMENTARY, UK NATIONAL LEAGUE, UK ESPN PPV, UK ENTERTAINMENT, UK WORLD SPORTS, UK UFC/BOXING PPV, NL ODIDO VERMAAK ᴴᴰ ᴳᴼᴸᴰ, UK SKY SPORTS RED BUTTON, and 28266 VOD Directories, VOD exemple: EN 4K MAFIA & GANGSTER, EN SCHOOLING KIDS, EN WAR, EN DRAMA, NL OVERIGE, NETFLIX KIDS, EN GANGSTER & MAFIA, NL VIDEOLAND, EN RATED R, NL NETFLIX, , IPTV subscription Expire on : 08/12/2024.
SEO Tips:
Explain the product in detail, focusing on its benefits, the IPTV provider platform name: PROTV, usability, and the available streaming content. Include the keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, naturally and use LSI/NLP related keywords to the post title.
Ensure to include in the end of the body the first Call to Action NATURALLY in 1 paragraph, Encourage potential customers to take action, by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously..
Ensure to include after the end of the body the Second Call to Action NATURALLY in 1 paragraph, Encourage visitors to take action, by joining our Pintrest Page https://www.pinterest.com/IPTV_SMARTERS to get the latest trial IPTV codes.
Incorporate the primary keyword: Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free, and related keywords naturally throughout the excerpt, introduction and body.
Use HTML tags for better formatting and readability, such as <div>, <b>, <i>, <ul>, <li><table>, and <p> in body.
Ensure the use of a modern and stylish HTML design in the body display by using inline style = '',
Highlight the body into a stylish and modern <div>,
Clearly Mention and Highlight the platform name: PROTV with stylish text decoration using inline style.
Highlight the platform name: IPTV20SAVE at checkout for 20% off with stylish text decoration using inline style.
Highlight the first CTA: by using the discount code IPTV20SAVE at checkout for 20% off when purchasing our premium membership at https://Iptv2live.com/premium-membership and unlock all premium IPTV subscriptions simultaneously. with a proper anchor texts into a new line centered HTML button tag.
Highlight the second CTA: by joining our Pintrest Page https://www.pinterest.com/IPTV_SMARTERS to get the latest trial IPTV codes with a proper anchor text into a new line centered HTML button tag.
Avoid keyword stuffing; focus on creating valuable, informative content for the reader.
Make sure the product description is engaging, informative, and optimized for search engines while providing value to the reader.
Make sure to never mention the word "Call to Action" in your response.
Finally make sure that the output overall pass all AI test detection by using Humanized and natural Text writing style.```json
{
"excerpt": "Experience a risk-free 14-day trial of our premium IPTV service, starting from 23/11/2024, with access to thousands of live streaming channels and VOD directories.",
"introduction": "Discover unparalleled entertainment with our <b>Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free</b>. This exclusive offer allows you to explore a vast selection of live channels and on-demand content without any financial risk.",
"head1": "Free 14-Day IPTV Trial",
"body": "<div style='font-family: Arial, sans-serif; line-height: 1.6; padding: 15px; background-color: #f9f9f9; border-radius: 5px;'>\n <h3 style='color: #333;'>Why Choose Our IPTV Service?</h3>\n <p style='color: #555;'>With our <b style='text-decoration: underline;'>PROTV</b> subscription, enjoy a rich diversity of entertainment options. Our IPTV platform is designed to cater to every viewer's tastes with over 5599 live streaming categories and an extensive VOD library. Experience everything from UK national sports to blockbuster movies in stunning HD quality.</p>\n <h4 style='color: #333;'>Key Features of PROTV IPTV Subscription</h4>\n <p style='color: #555;'>Our IPTV offerings include: \n <ul>\n <li>UK NEWS, UK GENERAL, BE DOCUMENTAIRE</li>\n <li>UK LIGUE 1 PPV, UK PDC BOARD PPV</li>\n <li>UK ENTERTAINMENT HEVC, USA, UK DOCUMENTARY</li>\n <li>UK WORLD SPORTS, UK UFC/BOXING PPV</li>\n <li>28266 VOD Directories including EN 4K MAFIA & GANGSTER</li>\n </ul>\n More categories include exclusive content like NL HBO MAX, Netflix Kids, and a selection of critically acclaimed films to cater to all preferences.</p>\n <p style='color: #555;'>Your <b style='text-decoration: underline;'>Free Test IPTV For 14 Days - starting from 23/11/2024 risk-free</b> trial ends on 08/12/2024. This is the perfect opportunity to experience <b style='text-decoration: underline;'>PROTV</b> without any financial commitment. Examine our user-friendly interface that guarantees seamless navigation to your favorite programs.</p>\n <p style='color: #555;'>Join our platform and witness why IPTV is the future of entertainment. Offering remarkable picture quality, extensive channel options, and a diverse selection of VOD content, you will find something to enjoy for everyone in the family!</p>\n <p style='color: #555;'>Unlock even more benefits by using the discount code <b style='text-decoration: underline;'>IPTV20SAVE</b> at checkout for 20% off when purchasing our premium membership at <a href='https://Iptv2live.com/premium-membership' style='color: #007BFF; text-decoration: none;'>https://Iptv2live.com/premium-membership</a> and unlock all premium IPTV subscriptions simultaneously.</p>\n <div style='text-align: center;'>\n <a href='https://Iptv2live.com/premium-membership' style='display: inline-block; padding: 10px 20px; background-color: #28a745; color: white; text-decoration: none; border-radius: 5px;'>Get Premium Membership Now!</a>\n </div>\n <p style='color: #555;'>Stay updated with the latest offers by following us on Pinterest. Join us at <a href='https://www.pinterest.com/IPTV_SMARTERS' style='color: #007BFF; text-decoration: none;'>https://www.pinterest.com/IPTV_SMARTERS</a> to get the latest trial IPTV codes and never miss out on a great deal!</p>\n <div style='text-align: center;'>\n <a href='https://www.pinterest.com/IPTV_SMARTERS' style='display: inline-block; padding: 10px 20px; background-color: #007BFF; color: white; text-decoration: none; border-radius: 5px;'>Follow Us on Pinterest</a>\n </div>\n</div>"
}
```