Basic loot UI mostly working. Each time you open there's a torch and you can place it visually on any slot on your character.
This commit is contained in:
parent
4b34de2109
commit
5c47a0151c
13 changed files with 123 additions and 46 deletions
4
Makefile
4
Makefile
|
@ -26,7 +26,7 @@ tracy_build:
|
||||||
meson compile -j 10 -C builddir
|
meson compile -j 10 -C builddir
|
||||||
|
|
||||||
test: build
|
test: build
|
||||||
./builddir/runtests "[loot]"
|
./builddir/runtests
|
||||||
|
|
||||||
run: build test
|
run: build test
|
||||||
ifeq '$(OS)' 'Windows_NT'
|
ifeq '$(OS)' 'Windows_NT'
|
||||||
|
@ -49,7 +49,7 @@ clean:
|
||||||
meson compile --clean -C builddir
|
meson compile --clean -C builddir
|
||||||
|
|
||||||
debug_test: build
|
debug_test: build
|
||||||
gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[loot]"
|
gdb --nx -x .gdbinit --ex run --args builddir/runtests -e
|
||||||
|
|
||||||
win_installer:
|
win_installer:
|
||||||
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
constexpr const int INV_SLOTS=20;
|
constexpr const int INV_SLOTS=16;
|
||||||
constexpr const int TEXTURE_WIDTH=256;
|
constexpr const int TEXTURE_WIDTH=256;
|
||||||
constexpr const int TEXTURE_HEIGHT=256;
|
constexpr const int TEXTURE_HEIGHT=256;
|
||||||
constexpr const int RAY_VIEW_WIDTH=900;
|
constexpr const int RAY_VIEW_WIDTH=900;
|
||||||
|
|
|
@ -5,7 +5,8 @@ namespace Events {
|
||||||
START, COMBAT, LOOT, DEATH, STAIRS_UP, STAIRS_DOWN, TRAP,
|
START, COMBAT, LOOT, DEATH, STAIRS_UP, STAIRS_DOWN, TRAP,
|
||||||
COMBAT_START, NO_NEIGHBORS, HP_STATUS,
|
COMBAT_START, NO_NEIGHBORS, HP_STATUS,
|
||||||
ATTACK, BLOCK, EVADE, NEW_RITUAL,
|
ATTACK, BLOCK, EVADE, NEW_RITUAL,
|
||||||
UPDATE_SPRITE, ENEMY_SPAWN, NOOP, LOOT_CLOSE
|
UPDATE_SPRITE, ENEMY_SPAWN, NOOP,
|
||||||
|
LOOT_CLOSE, LOOT_SELECT, LOOT_PLACE
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Combat {
|
struct Combat {
|
||||||
|
|
34
gui/fsm.cpp
34
gui/fsm.cpp
|
@ -38,8 +38,16 @@ namespace gui {
|
||||||
FSM_STATE(State, IN_COMBAT, ev);
|
FSM_STATE(State, IN_COMBAT, ev);
|
||||||
FSM_STATE(State, COMBAT_ROTATE, ev);
|
FSM_STATE(State, COMBAT_ROTATE, ev);
|
||||||
FSM_STATE(State, NEXT_LEVEL, ev);
|
FSM_STATE(State, NEXT_LEVEL, ev);
|
||||||
FSM_STATE(State, LOOTING, ev);
|
|
||||||
FSM_STATE(State, END, ev);
|
FSM_STATE(State, END, ev);
|
||||||
|
FSM_STATE(State, LOOTING, ev, std::make_any<bool>(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSM::event(Event ev, std::any data) {
|
||||||
|
switch($state) {
|
||||||
|
FSM_STATE(State, LOOTING, ev, data);
|
||||||
|
default:
|
||||||
|
dbc::log(fmt::format("event given with data but event={} is not handled", int(ev)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +118,7 @@ namespace gui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSM::LOOTING(Event ev) {
|
void FSM::LOOTING(Event ev, std::any data) {
|
||||||
using enum Event;
|
using enum Event;
|
||||||
|
|
||||||
switch(ev) {
|
switch(ev) {
|
||||||
|
@ -118,6 +126,19 @@ namespace gui {
|
||||||
$loot_ui.active = false;
|
$loot_ui.active = false;
|
||||||
state(State::IDLE);
|
state(State::IDLE);
|
||||||
break;
|
break;
|
||||||
|
case LOOT_SELECT: {
|
||||||
|
int slot_id = std::any_cast<int>(data);
|
||||||
|
|
||||||
|
if(auto entity = $loot_ui.select_slot(slot_id)) {
|
||||||
|
fmt::println("LOOT SELECT slot={} has entity={}", slot_id, entity.value());
|
||||||
|
$status_ui.select_slot(slot_id, entity.value());
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case LOOT_PLACE: {
|
||||||
|
std::string slot_name = std::any_cast<std::string>(data);
|
||||||
|
int slot_id = $status_ui.place_slot(slot_name);
|
||||||
|
$loot_ui.remove_slot(slot_id);
|
||||||
|
} break;
|
||||||
case TICK:
|
case TICK:
|
||||||
// do nothing
|
// do nothing
|
||||||
break;
|
break;
|
||||||
|
@ -190,6 +211,9 @@ namespace gui {
|
||||||
state(State::LOOTING);
|
state(State::LOOTING);
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
|
case LOOT_PLACE:
|
||||||
|
// ignored
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
dbc::sentinel("unhandled event in IDLE");
|
dbc::sentinel("unhandled event in IDLE");
|
||||||
}
|
}
|
||||||
|
@ -439,6 +463,12 @@ namespace gui {
|
||||||
// BUG: need to resolve GUI events vs. FSM events better
|
// BUG: need to resolve GUI events vs. FSM events better
|
||||||
event(Event::LOOT_OPEN);
|
event(Event::LOOT_OPEN);
|
||||||
break;
|
break;
|
||||||
|
case eGUI::LOOT_SELECT:
|
||||||
|
event(Event::LOOT_SELECT, data);
|
||||||
|
break;
|
||||||
|
case eGUI::LOOT_PLACE:
|
||||||
|
event(Event::LOOT_PLACE, data);
|
||||||
|
break;
|
||||||
case eGUI::LOOT: {
|
case eGUI::LOOT: {
|
||||||
$map_ui.log(L"You picked up an item.");
|
$map_ui.log(L"You picked up an item.");
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -41,7 +41,9 @@ namespace gui {
|
||||||
STOP_COMBAT = 12,
|
STOP_COMBAT = 12,
|
||||||
STAIRS_DOWN = 13,
|
STAIRS_DOWN = 13,
|
||||||
LOOT_OPEN=14,
|
LOOT_OPEN=14,
|
||||||
QUIT = 15
|
LOOT_SELECT=15,
|
||||||
|
LOOT_PLACE=16,
|
||||||
|
QUIT = 17
|
||||||
};
|
};
|
||||||
|
|
||||||
class FSM : public DeadSimpleFSM<State, Event> {
|
class FSM : public DeadSimpleFSM<State, Event> {
|
||||||
|
@ -66,6 +68,7 @@ namespace gui {
|
||||||
FSM();
|
FSM();
|
||||||
|
|
||||||
void event(Event ev);
|
void event(Event ev);
|
||||||
|
void event(Event ev, std::any data);
|
||||||
void autowalk();
|
void autowalk();
|
||||||
void start_autowalk(double rot_speed);
|
void start_autowalk(double rot_speed);
|
||||||
|
|
||||||
|
@ -78,7 +81,7 @@ namespace gui {
|
||||||
void IN_COMBAT(Event ev);
|
void IN_COMBAT(Event ev);
|
||||||
void COMBAT_ROTATE(Event ev);
|
void COMBAT_ROTATE(Event ev);
|
||||||
void NEXT_LEVEL(Event ev);
|
void NEXT_LEVEL(Event ev);
|
||||||
void LOOTING(Event ev);
|
void LOOTING(Event ev, std::any data);
|
||||||
void END(Event ev);
|
void END(Event ev);
|
||||||
|
|
||||||
void try_move(int dir, bool strafe);
|
void try_move(int dir, bool strafe);
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace guecs {
|
||||||
|
|
||||||
Clickable make_action(DinkyECS::World& target, Events::GUI event) {
|
Clickable make_action(DinkyECS::World& target, Events::GUI event) {
|
||||||
return {[&, event](auto ent, auto data){
|
return {[&, event](auto ent, auto data){
|
||||||
|
// BUG: I think entityt here shifted and isn't part of the world anymore
|
||||||
|
// BUG: it's actually coming from the GUI so passing it here is wrong
|
||||||
// remember that ent is passed in from the UI::mouse handler
|
// remember that ent is passed in from the UI::mouse handler
|
||||||
target.send<Events::GUI>(event, ent, data);
|
target.send<Events::GUI>(event, ent, data);
|
||||||
}};
|
}};
|
||||||
|
@ -11,6 +13,8 @@ namespace guecs {
|
||||||
|
|
||||||
Clickable make_action(DinkyECS::World& target, Events::GUI event, std::any data) {
|
Clickable make_action(DinkyECS::World& target, Events::GUI event, std::any data) {
|
||||||
return {[&, event, data](auto ent, auto){
|
return {[&, event, data](auto ent, auto){
|
||||||
|
// BUG: I think entityt here shifted and isn't part of the world anymore
|
||||||
|
// BUG: it's actually coming from the GUI so passing it here is wrong
|
||||||
// remember that ent is passed in from the UI::mouse handler
|
// remember that ent is passed in from the UI::mouse handler
|
||||||
target.send<Events::GUI>(event, ent, data);
|
target.send<Events::GUI>(event, ent, data);
|
||||||
}};
|
}};
|
||||||
|
|
|
@ -33,34 +33,57 @@ namespace gui {
|
||||||
$gui.set<guecs::Clickable>(close,
|
$gui.set<guecs::Clickable>(close,
|
||||||
guecs::make_action(*$level.world, Events::GUI::LOOT_CLOSE));
|
guecs::make_action(*$level.world, Events::GUI::LOOT_CLOSE));
|
||||||
|
|
||||||
|
for(int i = 0; i < INV_SLOTS; i++) {
|
||||||
|
auto id = $gui.entity("item_", i);
|
||||||
|
$gui.set<guecs::Rectangle>(id, {THEME.PADDING,
|
||||||
|
THEME.TRANSPARENT, THEME.LIGHT_MID });
|
||||||
|
$gui.set<guecs::Effect>(id, {0.4f, "ui_shader"});
|
||||||
|
$gui.set<guecs::Clickable>(id, {
|
||||||
|
guecs::make_action(*$level.world, Events::GUI::LOOT_SELECT, {i})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$gui.init();
|
$gui.init();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LootUI::update() {
|
std::optional<DinkyECS::Entity> LootUI::select_slot(int slot_id) {
|
||||||
dbc::check(contents.size() < 16, "too many items in loot contents, must be < 16");
|
if(size_t(slot_id) < contents.size()) {
|
||||||
for(int i = 0; i < 16; i++) {
|
return contents.at(slot_id);
|
||||||
auto id = $gui.entity("item_", i);
|
} else {
|
||||||
if($gui.has<guecs::Rectangle>(id)) {
|
return std::nullopt;
|
||||||
$gui.remove<guecs::Rectangle>(id);
|
|
||||||
$gui.remove<guecs::Effect>(id);
|
|
||||||
$gui.remove<guecs::Clickable>(id);
|
|
||||||
$gui.remove<guecs::Sprite>(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(size_t item_i = 0; item_i < contents.size(); item_i++) {
|
void LootUI::remove_slot(int slot_id) {
|
||||||
auto id = $gui.entity("item_", int(item_i));
|
dbc::check(size_t(slot_id) < contents.size(),
|
||||||
$gui.set_init<guecs::Rectangle>(id, {THEME.PADDING,
|
fmt::format("invalid slot id {} give, contents.size={}",
|
||||||
THEME.TRANSPARENT, THEME.LIGHT_MID });
|
slot_id, contents.size()));
|
||||||
$gui.set_init<guecs::Effect>(id, {0.4f, "ui_shader"});
|
|
||||||
$gui.set<guecs::Clickable>(id, {
|
|
||||||
[=](auto, auto) { fmt::println("clicked button"); }
|
|
||||||
});
|
|
||||||
|
|
||||||
auto item = contents.at(item_i);
|
contents.erase(contents.begin() + slot_id);
|
||||||
auto& sprite = $level.world->get<components::Sprite>(item);
|
update();
|
||||||
$gui.set_init<guecs::Sprite>(id, {sprite.name});
|
}
|
||||||
|
|
||||||
|
void LootUI::update() {
|
||||||
|
dbc::check(contents.size() < INV_SLOTS, "too many items in loot contents, must be < 16");
|
||||||
|
|
||||||
|
for(size_t i = 0; i < INV_SLOTS; i++) {
|
||||||
|
auto id = $gui.entity("item_", int(i));
|
||||||
|
|
||||||
|
fmt::println("checking for sprite at {}", id);
|
||||||
|
if($gui.has<guecs::Sprite>(id)) {
|
||||||
|
fmt::println("REMOVING SPRITE {}", id);
|
||||||
|
$gui.remove<guecs::Sprite>(id);
|
||||||
|
} else {
|
||||||
|
fmt::println("nothing at {}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i < contents.size()) {
|
||||||
|
auto item = contents.at(i);
|
||||||
|
auto& sprite = $level.world->get<components::Sprite>(item);
|
||||||
|
fmt::println("NEW SPRITE SPRITE {}", sprite.name);
|
||||||
|
$gui.set_init<guecs::Sprite>(id, {sprite.name});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,5 +20,7 @@ namespace gui {
|
||||||
void render(sf::RenderWindow& window);
|
void render(sf::RenderWindow& window);
|
||||||
void update_level(GameLevel &level);
|
void update_level(GameLevel &level);
|
||||||
bool mouse(float x, float y, bool hover);
|
bool mouse(float x, float y, bool hover);
|
||||||
|
std::optional<DinkyECS::Entity> select_slot(int slot);
|
||||||
|
void remove_slot(int slot_id);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <guecs/ui.hpp>
|
#include <guecs/ui.hpp>
|
||||||
#include "rand.hpp"
|
#include "rand.hpp"
|
||||||
#include <fmt/xchar.h>
|
#include <fmt/xchar.h>
|
||||||
|
#include "gui/guecstra.hpp"
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
using namespace guecs;
|
using namespace guecs;
|
||||||
|
@ -19,13 +20,6 @@ namespace gui {
|
||||||
"[hand_r|_|_ |hand_l]"
|
"[hand_r|_|_ |hand_l]"
|
||||||
"[ring_r|_|_ |ring_l]"
|
"[ring_r|_|_ |ring_l]"
|
||||||
"[pocket_r|armor_leg|pocket_l]");
|
"[pocket_r|armor_leg|pocket_l]");
|
||||||
|
|
||||||
size_t inv_id = 0;
|
|
||||||
for(auto [name, entity] : $gui.$name_ents) {
|
|
||||||
if(name.starts_with("inv_")) {
|
|
||||||
$slots[name] = inv_id++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusUI::init() {
|
void StatusUI::init() {
|
||||||
|
@ -49,7 +43,7 @@ namespace gui {
|
||||||
} else {
|
} else {
|
||||||
$gui.set<Textual>(button, {guecs::to_wstring(name)});
|
$gui.set<Textual>(button, {guecs::to_wstring(name)});
|
||||||
$gui.set<Clickable>(button, {
|
$gui.set<Clickable>(button, {
|
||||||
[this](auto ent, auto data){ select_slot(ent, data); }
|
guecs::make_action(*$level.world, Events::GUI::LOOT_PLACE, {name})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,13 +65,6 @@ namespace gui {
|
||||||
$ritual_ui.event(ritual::Event::TOGGLE);
|
$ritual_ui.event(ritual::Event::TOGGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusUI::select_slot(DinkyECS::Entity ent, any slot_name) {
|
|
||||||
(void)ent;
|
|
||||||
(void)slot_name;
|
|
||||||
dbc::log("REWRITE!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* WARNING: This is really not the greatest way to do this. */
|
|
||||||
void StatusUI::update() {
|
void StatusUI::update() {
|
||||||
dbc::log("REWRITE ME!");
|
dbc::log("REWRITE ME!");
|
||||||
}
|
}
|
||||||
|
@ -92,4 +79,22 @@ namespace gui {
|
||||||
$level = level;
|
$level = level;
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StatusUI::select_slot(int slot_id, DinkyECS::Entity entity) {
|
||||||
|
$selected_slot = slot_id;
|
||||||
|
$selected_entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatusUI::place_slot(const std::string &name) {
|
||||||
|
fmt::println("LOOT slot={}, entity={} PLACE into slot={}",
|
||||||
|
$selected_slot, $selected_entity, name);
|
||||||
|
|
||||||
|
auto& sprite = $level.world->get<components::Sprite>($selected_entity);
|
||||||
|
auto gui_id = $gui.entity(name);
|
||||||
|
$gui.set_init<guecs::Sprite>(gui_id, {sprite.name});
|
||||||
|
|
||||||
|
$slots.insert_or_assign(name, $selected_entity);
|
||||||
|
|
||||||
|
return $selected_slot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,20 @@ namespace gui {
|
||||||
class StatusUI {
|
class StatusUI {
|
||||||
public:
|
public:
|
||||||
guecs::UI $gui;
|
guecs::UI $gui;
|
||||||
std::map<std::string, size_t> $slots;
|
std::unordered_map<std::string, DinkyECS::Entity> $slots;
|
||||||
GameLevel $level;
|
GameLevel $level;
|
||||||
ritual::UI $ritual_ui;
|
ritual::UI $ritual_ui;
|
||||||
|
int $selected_slot;
|
||||||
|
DinkyECS::Entity $selected_entity;
|
||||||
|
|
||||||
StatusUI(GameLevel level);
|
StatusUI(GameLevel level);
|
||||||
void select_slot(DinkyECS::Entity ent, std::any data);
|
|
||||||
void select_ritual();
|
void select_ritual();
|
||||||
void update_level(GameLevel &level);
|
void update_level(GameLevel &level);
|
||||||
bool mouse(float x, float y, bool hover);
|
bool mouse(float x, float y, bool hover);
|
||||||
void init();
|
void init();
|
||||||
void render(sf::RenderWindow &window);
|
void render(sf::RenderWindow &window);
|
||||||
void update();
|
void update();
|
||||||
|
void select_slot(int slot_id, DinkyECS::Entity entity);
|
||||||
|
int place_slot(const std::string &name);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ json load_test_data(const string &fname) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("camera control", "[map]") {
|
TEST_CASE("camera control", "[map]") {
|
||||||
|
components::init();
|
||||||
LevelManager levels;
|
LevelManager levels;
|
||||||
GameLevel level = levels.current();
|
GameLevel level = levels.current();
|
||||||
auto &map = *level.map;
|
auto &map = *level.map;
|
||||||
|
@ -32,6 +33,7 @@ TEST_CASE("camera control", "[map]") {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("map placement test", "[map:placement]") {
|
TEST_CASE("map placement test", "[map:placement]") {
|
||||||
|
components::init();
|
||||||
for(int i = 0; i < 20; i++) {
|
for(int i = 0; i < 20; i++) {
|
||||||
LevelManager levels;
|
LevelManager levels;
|
||||||
GameLevel level = levels.current();
|
GameLevel level = levels.current();
|
||||||
|
|
|
@ -256,6 +256,7 @@ TEST_CASE("prototype circle algorithm", "[matrix:circle]") {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("viewport iterator", "[matrix:viewport]") {
|
TEST_CASE("viewport iterator", "[matrix:viewport]") {
|
||||||
|
components::init();
|
||||||
size_t width = Random::uniform<size_t>(20, 22);
|
size_t width = Random::uniform<size_t>(20, 22);
|
||||||
size_t height = Random::uniform<size_t>(21, 25);
|
size_t height = Random::uniform<size_t>(21, 25);
|
||||||
shared_ptr<Map> map = make_map();
|
shared_ptr<Map> map = make_map();
|
||||||
|
@ -279,6 +280,7 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("random rectangle", "[matrix:rando_rect]") {
|
TEST_CASE("random rectangle", "[matrix:rando_rect]") {
|
||||||
|
components::init();
|
||||||
for(int i = 0; i < 5; i++) {
|
for(int i = 0; i < 5; i++) {
|
||||||
shared_ptr<Map> map = make_map();
|
shared_ptr<Map> map = make_map();
|
||||||
map->invert_space();
|
map->invert_space();
|
||||||
|
@ -303,6 +305,7 @@ TEST_CASE("random rectangle", "[matrix:rando_rect]") {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("standard rectangle", "[matrix:rectangle]") {
|
TEST_CASE("standard rectangle", "[matrix:rectangle]") {
|
||||||
|
components::init();
|
||||||
for(int i = 0; i < 5; i++) {
|
for(int i = 0; i < 5; i++) {
|
||||||
shared_ptr<Map> map = make_map();
|
shared_ptr<Map> map = make_map();
|
||||||
auto wall_copy = map->walls();
|
auto wall_copy = map->walls();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
using namespace fmt;
|
using namespace fmt;
|
||||||
|
|
||||||
TEST_CASE("test texture management", "[textures]") {
|
TEST_CASE("test texture management", "[textures]") {
|
||||||
|
components::init();
|
||||||
textures::init();
|
textures::init();
|
||||||
auto spider = textures::get("hairy_spider");
|
auto spider = textures::get("hairy_spider");
|
||||||
REQUIRE(spider.sprite != nullptr);
|
REQUIRE(spider.sprite != nullptr);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue