2023-06-20 04:33:09 +00:00
|
|
|
#include "StarMaterialItem.hpp"
|
|
|
|
#include "StarJson.hpp"
|
2023-08-18 13:14:53 +00:00
|
|
|
#include "StarJsonExtra.hpp"
|
2023-06-20 04:33:09 +00:00
|
|
|
#include "StarMaterialDatabase.hpp"
|
|
|
|
#include "StarRoot.hpp"
|
|
|
|
#include "StarAssets.hpp"
|
|
|
|
#include "StarWorld.hpp"
|
|
|
|
#include "StarWorldClient.hpp"
|
|
|
|
#include "StarWorldTemplate.hpp"
|
2023-08-19 10:47:58 +00:00
|
|
|
#include "StarInput.hpp"
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
MaterialItem::MaterialItem(Json const& config, String const& directory, Json const& settings)
|
|
|
|
: Item(config, directory, settings), FireableItem(config), BeamItem(config) {
|
|
|
|
m_material = config.getInt("materialId");
|
|
|
|
m_materialHueShift = materialHueFromDegrees(instanceValue("materialHueShift", 0).toFloat());
|
|
|
|
|
|
|
|
if (materialHueShift() != MaterialHue()) {
|
|
|
|
auto drawables = iconDrawables();
|
|
|
|
for (auto& d : drawables) {
|
2023-06-24 12:49:47 +00:00
|
|
|
if (d.isImage()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("?hueshift={}", materialHueToDegrees(m_materialHueShift));
|
2023-06-24 12:49:47 +00:00
|
|
|
d.imagePart().addDirectives(image, false);
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
setIconDrawables(move(drawables));
|
|
|
|
}
|
|
|
|
|
|
|
|
setTwoHanded(config.getBool("twoHanded", true));
|
|
|
|
|
2023-07-31 06:00:19 +00:00
|
|
|
auto defaultParameters = Root::singleton().assets()->json("/items/defaultParameters.config");
|
|
|
|
setCooldownTime(config.queryFloat("materialItems.cooldown", defaultParameters.queryFloat("materialItems.cooldown")));
|
|
|
|
m_blockRadius = config.getFloat("blockRadius", defaultParameters.getFloat("blockRadius"));
|
|
|
|
m_altBlockRadius = config.getFloat("altBlockRadius", defaultParameters.getFloat("altBlockRadius"));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2023-08-18 13:14:53 +00:00
|
|
|
auto materialDatabase = Root::singleton().materialDatabase();
|
|
|
|
m_multiplace = config.getBool("allowMultiplace", BlockCollisionSet.contains(materialDatabase->materialCollisionKind(m_material)));
|
|
|
|
m_placeSounds = jsonToStringList(config.get("placeSounds", JsonArray()));
|
|
|
|
if (m_placeSounds.empty()) {
|
|
|
|
auto miningSound = materialDatabase->miningSound(m_material);
|
|
|
|
if (!miningSound.empty())
|
|
|
|
m_placeSounds.append(move(miningSound));
|
|
|
|
auto stepSound = materialDatabase->footstepSound(m_material);
|
|
|
|
if (!stepSound.empty())
|
|
|
|
m_placeSounds.append(move(stepSound));
|
|
|
|
else if (m_placeSounds.empty())
|
|
|
|
m_placeSounds.append(materialDatabase->defaultFootstepSound());
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
m_shifting = false;
|
2023-08-19 10:47:58 +00:00
|
|
|
m_lastTileAreaRadiusCache = 0.0f;
|
|
|
|
m_collisionOverride = TileCollisionOverride::None;
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ItemPtr MaterialItem::clone() const {
|
|
|
|
return make_shared<MaterialItem>(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MaterialItem::init(ToolUserEntity* owner, ToolHand hand) {
|
|
|
|
FireableItem::init(owner, hand);
|
|
|
|
BeamItem::init(owner, hand);
|
2023-08-19 10:47:58 +00:00
|
|
|
owner->addSound(Random::randValueFrom(m_placeSounds), 1.0f, 2.0f);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-07-31 06:41:12 +00:00
|
|
|
void MaterialItem::uninit() {
|
|
|
|
m_lastAimPosition.reset();
|
|
|
|
}
|
|
|
|
|
2023-07-20 14:58:49 +00:00
|
|
|
void MaterialItem::update(float dt, FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
|
|
|
FireableItem::update(dt, fireMode, shifting, moves);
|
|
|
|
BeamItem::update(dt, fireMode, shifting, moves);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (shifting || !multiplaceEnabled())
|
|
|
|
setEnd(BeamItem::EndType::Tile);
|
|
|
|
else
|
|
|
|
setEnd(BeamItem::EndType::TileGroup);
|
|
|
|
m_shifting = shifting;
|
2023-08-19 10:47:58 +00:00
|
|
|
|
|
|
|
if (auto presses = Input::singleton().bindDown("opensb", "materialCycleCollision")) {
|
|
|
|
CollisionKind baseKind = Root::singleton().materialDatabase()->materialCollisionKind(m_material);
|
|
|
|
for (size_t i = 0; i != *presses; ++i) {
|
|
|
|
constexpr auto limit = (uint8_t)TileCollisionOverride::Block + 1;
|
|
|
|
while (true) {
|
|
|
|
m_collisionOverride = (TileCollisionOverride)(((uint8_t)m_collisionOverride + 1) % limit);
|
|
|
|
if (collisionKindFromOverride(m_collisionOverride) != baseKind)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
owner()->addSound("/sfx/tools/cyclematcollision.ogg", 1.0f, Random::randf(0.9f, 1.1f));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MaterialItem::render(RenderCallback* renderCallback, EntityRenderLayer renderLayer) {
|
|
|
|
if (m_collisionOverride != TileCollisionOverride::None) {
|
|
|
|
float pulseLevel = 1.f - 0.3f * 0.5f * ((float)sin(2 * Constants::pi * 4.0 * Time::monotonicTime()) + 1.f);
|
|
|
|
Color color = Color::rgba(owner()->favoriteColor()).mix(Color::White);
|
|
|
|
color.setAlphaF(color.alphaF() * pulseLevel * 0.95f);
|
|
|
|
auto addIndicator = [&](String const& path) {
|
|
|
|
Vec2F basePosition = Vec2F(0.5f, 0.5f);
|
|
|
|
auto indicator = Drawable::makeImage(path, 1.0f / TilePixels, true, basePosition);
|
|
|
|
indicator.fullbright = true;
|
|
|
|
indicator.color = color;
|
2023-08-19 11:35:56 +00:00
|
|
|
for (auto& tilePos : tileArea(calcRadius(m_shifting), owner()->aimPosition())) {
|
2023-08-19 10:47:58 +00:00
|
|
|
indicator.position = basePosition + Vec2F(tilePos);
|
|
|
|
renderCallback->addDrawable(indicator, RenderLayerForegroundTile);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (m_collisionOverride == TileCollisionOverride::Empty)
|
|
|
|
addIndicator("/interface/building/collisionempty.png");
|
|
|
|
else if (m_collisionOverride == TileCollisionOverride::Platform)
|
|
|
|
addIndicator("/interface/building/collisionplatform.png");
|
|
|
|
else if (m_collisionOverride == TileCollisionOverride::Block)
|
|
|
|
addIndicator("/interface/building/collisionblock.png");
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
List<Drawable> MaterialItem::nonRotatedDrawables() const {
|
|
|
|
return beamDrawables(canPlace(m_shifting));
|
|
|
|
}
|
|
|
|
|
|
|
|
void MaterialItem::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
2023-08-19 12:49:05 +00:00
|
|
|
if (!initialized() || !ready())
|
2023-06-20 04:33:09 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
auto layer = (mode == FireMode::Primary || !twoHanded() ? TileLayer::Foreground : TileLayer::Background);
|
|
|
|
TileModificationList modifications;
|
|
|
|
|
2023-08-19 10:47:58 +00:00
|
|
|
float radius = calcRadius(shifting);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2023-07-31 06:00:19 +00:00
|
|
|
auto geo = world()->geometry();
|
|
|
|
auto aimPosition = owner()->aimPosition();
|
2023-08-19 10:47:58 +00:00
|
|
|
|
2023-07-31 06:00:19 +00:00
|
|
|
if (!m_lastAimPosition)
|
|
|
|
m_lastAimPosition = aimPosition;
|
|
|
|
|
|
|
|
unsigned steps = 1;
|
|
|
|
Vec2F diff = {};
|
|
|
|
if (*m_lastAimPosition != aimPosition) {
|
|
|
|
diff = geo.diff(*m_lastAimPosition, aimPosition);
|
|
|
|
float magnitude = diff.magnitude();
|
2023-08-18 14:18:51 +00:00
|
|
|
float limit = max(4.f, 64.f / radius);
|
|
|
|
if (magnitude > limit) {
|
2023-08-19 10:47:58 +00:00
|
|
|
diff = diff.normalized() * limit;
|
2023-08-18 14:18:51 +00:00
|
|
|
magnitude = limit;
|
2023-07-31 06:00:19 +00:00
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2023-08-19 11:35:56 +00:00
|
|
|
steps = (unsigned)ceil(magnitude * (Constants::pi / 2));
|
2023-07-31 06:00:19 +00:00
|
|
|
}
|
|
|
|
|
2023-08-19 02:34:35 +00:00
|
|
|
size_t total = 0;
|
2023-07-31 06:00:19 +00:00
|
|
|
for (int i = 0; i != steps; ++i) {
|
2023-08-18 13:28:31 +00:00
|
|
|
auto placementOrigin = aimPosition + diff * (1.0f - ((float)i / steps));
|
2023-08-19 12:49:05 +00:00
|
|
|
if (!owner()->inToolRange(placementOrigin))
|
|
|
|
continue;
|
|
|
|
|
2023-08-19 11:35:56 +00:00
|
|
|
for (Vec2I& pos : tileArea(radius, placementOrigin))
|
|
|
|
modifications.emplaceAppend(pos, PlaceMaterial{layer, materialId(), placementHueShift(pos), m_collisionOverride});
|
2023-07-31 06:00:19 +00:00
|
|
|
|
|
|
|
// Make sure not to make any more modifications than we have consumables.
|
|
|
|
if (modifications.size() > count())
|
|
|
|
modifications.resize(count());
|
|
|
|
size_t failed = world()->applyTileModifications(modifications, false).size();
|
|
|
|
if (failed < modifications.size()) {
|
2023-08-19 02:34:35 +00:00
|
|
|
size_t placed = modifications.size() - failed;
|
2023-08-18 13:14:53 +00:00
|
|
|
consume(placed);
|
|
|
|
total += placed;
|
2023-07-31 06:00:19 +00:00
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
2023-07-31 06:00:19 +00:00
|
|
|
|
2023-08-19 02:34:35 +00:00
|
|
|
if (total) {
|
2023-08-19 11:35:56 +00:00
|
|
|
float intensity = clamp(sqrt((float)total) / 16, 0.0f, 1.0f);
|
2023-08-19 02:34:35 +00:00
|
|
|
owner()->addSound(Random::randValueFrom(m_placeSounds), 1.0f + intensity, (1.125f - intensity * 0.75f) * Random::randf(0.9f, 1.1f));
|
2023-07-31 06:00:19 +00:00
|
|
|
FireableItem::fire(mode, shifting, edgeTriggered);
|
2023-08-18 13:14:53 +00:00
|
|
|
}
|
2023-07-31 06:00:19 +00:00
|
|
|
|
|
|
|
m_lastAimPosition = aimPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MaterialItem::endFire(FireMode mode, bool shifting) {
|
|
|
|
m_lastAimPosition.reset();
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MaterialId MaterialItem::materialId() const {
|
|
|
|
return m_material;
|
|
|
|
}
|
|
|
|
|
2023-08-19 10:47:58 +00:00
|
|
|
float MaterialItem::calcRadius(bool shifting) const {
|
|
|
|
if (!multiplaceEnabled())
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return !shifting ? m_blockRadius : m_altBlockRadius;
|
|
|
|
}
|
|
|
|
|
2023-08-19 11:35:56 +00:00
|
|
|
List<Vec2I>& MaterialItem::tileArea(float radius, Vec2F const& position) const {
|
|
|
|
if (m_lastTileAreaOriginCache != position || m_lastTileAreaRadiusCache != radius) {
|
|
|
|
m_lastTileAreaOriginCache = position;
|
2023-08-19 10:47:58 +00:00
|
|
|
m_lastTileAreaRadiusCache = radius;
|
2023-08-19 11:35:56 +00:00
|
|
|
m_tileAreasCache = tileAreaBrush(radius, position, true);
|
2023-08-19 10:47:58 +00:00
|
|
|
}
|
|
|
|
return m_tileAreasCache;
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
MaterialHue MaterialItem::materialHueShift() const {
|
|
|
|
return m_materialHueShift;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MaterialItem::canPlace(bool shifting) const {
|
|
|
|
if (initialized()) {
|
|
|
|
MaterialId material = materialId();
|
|
|
|
|
2023-08-19 10:47:58 +00:00
|
|
|
float radius = calcRadius(shifting);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2023-08-19 11:35:56 +00:00
|
|
|
for (auto& pos : tileArea(radius, owner()->aimPosition())) {
|
2023-06-20 04:33:09 +00:00
|
|
|
MaterialHue hueShift = placementHueShift(pos);
|
|
|
|
if (world()->canModifyTile(pos, PlaceMaterial{TileLayer::Foreground, material, hueShift}, false)
|
|
|
|
|| world()->canModifyTile(pos, PlaceMaterial{TileLayer::Background, material, hueShift}, false))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MaterialItem::multiplaceEnabled() const {
|
|
|
|
return m_multiplace && count() > 1;
|
|
|
|
}
|
|
|
|
|
2023-08-19 10:47:58 +00:00
|
|
|
float& MaterialItem::blockRadius() {
|
|
|
|
return m_blockRadius;
|
|
|
|
}
|
|
|
|
|
|
|
|
float& MaterialItem::altBlockRadius() {
|
|
|
|
return m_altBlockRadius;
|
|
|
|
}
|
|
|
|
|
|
|
|
TileCollisionOverride& MaterialItem::collisionOverride() {
|
|
|
|
return m_collisionOverride;
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
List<PreviewTile> MaterialItem::preview(bool shifting) const {
|
|
|
|
List<PreviewTile> result;
|
|
|
|
if (initialized()) {
|
|
|
|
Color lightColor = Color::rgba(owner()->favoriteColor());
|
|
|
|
Vec3B light = lightColor.toRgb();
|
|
|
|
|
|
|
|
auto material = materialId();
|
|
|
|
auto color = DefaultMaterialColorVariant;
|
|
|
|
|
|
|
|
size_t c = 0;
|
2023-08-19 11:35:56 +00:00
|
|
|
for (auto& pos : tileArea(calcRadius(shifting), owner()->aimPosition())) {
|
2023-06-20 04:33:09 +00:00
|
|
|
MaterialHue hueShift = placementHueShift(pos);
|
|
|
|
if (c >= count())
|
|
|
|
break;
|
|
|
|
if (world()->canModifyTile(pos, PlaceMaterial{TileLayer::Foreground, material, hueShift}, false)) {
|
|
|
|
result.append({pos, true, material, hueShift, true});
|
|
|
|
c++;
|
|
|
|
} else if (twoHanded()
|
|
|
|
&& world()->canModifyTile(pos, PlaceMaterial{TileLayer::Background, material, hueShift}, false)) {
|
|
|
|
result.append({pos, true, material, hueShift, true, light, true, color});
|
|
|
|
c++;
|
|
|
|
} else {
|
|
|
|
result.append({pos, true, material, hueShift, true});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
MaterialHue MaterialItem::placementHueShift(Vec2I const& pos) const {
|
|
|
|
if (auto hue = instanceValue("materialHueShift")) {
|
|
|
|
return materialHueFromDegrees(hue.toFloat());
|
|
|
|
} else if (auto worldClient = as<WorldClient>(world())) {
|
|
|
|
auto worldTemplate = worldClient->currentTemplate();
|
|
|
|
return worldTemplate->biomeMaterialHueShift(worldTemplate->blockBiomeIndex(pos[0], pos[1]), m_material);
|
|
|
|
} else {
|
|
|
|
return materialHueShift();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|