// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** crsshair.c Crosshair handling. ***************************************************************************/ #include "emu.h" #include "emuopts.h" #include "rendutil.h" #include "config.h" #include "xmlfile.h" #include "crsshair.h" /*************************************************************************** CONSTANTS ***************************************************************************/ #define CROSSHAIR_RAW_SIZE 100 #define CROSSHAIR_RAW_ROWBYTES ((CROSSHAIR_RAW_SIZE + 7) / 8) /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ /* raw bitmap */ static const u8 crosshair_raw_top[] = { 0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00, 0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x00, 0x00,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xf0,0x00, 0x01,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xf8,0x00, 0x03,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xfc,0x00, 0x07,0xfe,0x00,0x00,0x00,0x0f,0xfe,0x00,0x00,0x00,0x07,0xfe,0x00, 0x0f,0xff,0x00,0x00,0x01,0xff,0xff,0xf0,0x00,0x00,0x0f,0xff,0x00, 0x1f,0xff,0x80,0x00,0x1f,0xff,0xff,0xff,0x00,0x00,0x1f,0xff,0x80, 0x3f,0xff,0x80,0x00,0xff,0xff,0xff,0xff,0xe0,0x00,0x1f,0xff,0xc0, 0x7f,0xff,0xc0,0x03,0xff,0xff,0xff,0xff,0xf8,0x00,0x3f,0xff,0xe0, 0xff,0xff,0xe0,0x07,0xff,0xff,0xff,0xff,0xfc,0x00,0x7f,0xff,0xf0, 0x7f,0xff,0xf0,0x1f,0xff,0xff,0xff,0xff,0xff,0x00,0xff,0xff,0xe0, 0x3f,0xff,0xf8,0x7f,0xff,0xff,0xff,0xff,0xff,0xc1,0xff,0xff,0xc0, 0x0f,0xff,0xf8,0xff,0xff,0xff,0xff,0xff,0xff,0xe1,0xff,0xff,0x00, 0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfb,0xff,0xfe,0x00, 0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0x00, 0x01,0xff,0xff,0xff,0xff,0xf0,0x01,0xff,0xff,0xff,0xff,0xf8,0x00, 0x00,0x7f,0xff,0xff,0xff,0x00,0x00,0x1f,0xff,0xff,0xff,0xe0,0x00, 0x00,0x3f,0xff,0xff,0xf8,0x00,0x00,0x03,0xff,0xff,0xff,0xc0,0x00, 0x00,0x1f,0xff,0xff,0xe0,0x00,0x00,0x00,0xff,0xff,0xff,0x80,0x00, 0x00,0x0f,0xff,0xff,0x80,0x00,0x00,0x00,0x3f,0xff,0xff,0x00,0x00, 0x00,0x03,0xff,0xfe,0x00,0x00,0x00,0x00,0x0f,0xff,0xfc,0x00,0x00, 0x00,0x01,0xff,0xfc,0x00,0x00,0x00,0x00,0x07,0xff,0xf8,0x00,0x00, 0x00,0x03,0xff,0xf8,0x00,0x00,0x00,0x00,0x01,0xff,0xf8,0x00,0x00, 0x00,0x07,0xff,0xfc,0x00,0x00,0x00,0x00,0x03,0xff,0xfc,0x00,0x00, 0x00,0x0f,0xff,0xfe,0x00,0x00,0x00,0x00,0x07,0xff,0xfe,0x00,0x00, 0x00,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x0f,0xff,0xfe,0x00,0x00, 0x00,0x1f,0xff,0xff,0x80,0x00,0x00,0x00,0x1f,0xff,0xff,0x00,0x00, 0x00,0x1f,0xff,0xff,0x80,0x00,0x00,0x00,0x1f,0xff,0xff,0x00,0x00, 0x00,0x3f,0xfe,0xff,0xc0,0x00,0x00,0x00,0x3f,0xff,0xff,0x80,0x00, 0x00,0x7f,0xfc,0x7f,0xe0,0x00,0x00,0x00,0x7f,0xe7,0xff,0xc0,0x00, 0x00,0x7f,0xf8,0x3f,0xf0,0x00,0x00,0x00,0xff,0xc3,0xff,0xc0,0x00, 0x00,0xff,0xf8,0x1f,0xf8,0x00,0x00,0x01,0xff,0x83,0xff,0xe0,0x00, 0x00,0xff,0xf0,0x07,0xf8,0x00,0x00,0x01,0xfe,0x01,0xff,0xe0,0x00, 0x00,0xff,0xf0,0x03,0xfc,0x00,0x00,0x03,0xfc,0x01,0xff,0xe0,0x00, 0x01,0xff,0xe0,0x01,0xfe,0x00,0x00,0x07,0xf8,0x00,0xff,0xf0,0x00, 0x01,0xff,0xe0,0x00,0xff,0x00,0x00,0x0f,0xf0,0x00,0xff,0xf0,0x00, 0x01,0xff,0xc0,0x00,0x3f,0x80,0x00,0x1f,0xc0,0x00,0x7f,0xf0,0x00, 0x01,0xff,0xc0,0x00,0x1f,0x80,0x00,0x1f,0x80,0x00,0x7f,0xf0,0x00, 0x03,0xff,0xc0,0x00,0x0f,0xc0,0x00,0x3f,0x00,0x00,0x7f,0xf8,0x00, 0x03,0xff,0x80,0x00,0x07,0xe0,0x00,0x7e,0x00,0x00,0x3f,0xf8,0x00, 0x03,0xff,0x80,0x00,0x01,0xf0,0x00,0xf8,0x00,0x00,0x3f,0xf8,0x00, 0x03,0xff,0x80,0x00,0x00,0xf8,0x01,0xf0,0x00,0x00,0x3f,0xf8,0x00, 0x03,0xff,0x80,0x00,0x00,0x78,0x01,0xe0,0x00,0x00,0x3f,0xf8,0x00, 0x07,0xff,0x00,0x00,0x00,0x3c,0x03,0xc0,0x00,0x00,0x3f,0xfc,0x00, 0x07,0xff,0x00,0x00,0x00,0x0e,0x07,0x00,0x00,0x00,0x1f,0xfc,0x00, 0x07,0xff,0x00,0x00,0x00,0x07,0x0e,0x00,0x00,0x00,0x1f,0xfc,0x00, 0x07,0xff,0x00,0x00,0x00,0x03,0x9c,0x00,0x00,0x00,0x1f,0xfc,0x00, 0x07,0xff,0x00,0x00,0x00,0x01,0x98,0x00,0x00,0x00,0x1f,0xfc,0x00, 0x07,0xff,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x1f,0xfc,0x00 }; /* per-player colors */ static const rgb_t crosshair_colors[] = { rgb_t(0x40,0x40,0xff), rgb_t(0xff,0x40,0x40), rgb_t(0x40,0xff,0x40), rgb_t(0xff,0xff,0x40), rgb_t(0xff,0x40,0xff), rgb_t(0x40,0xff,0xff), rgb_t(0xff,0xff,0xff) }; //************************************************************************** // RENDER CROSSHAIR //************************************************************************** //------------------------------------------------- // render_crosshair - constructor //------------------------------------------------- render_crosshair::render_crosshair(running_machine &machine, int player) : m_machine(machine) , m_player(player) , m_used(false) , m_mode(CROSSHAIR_VISIBILITY_OFF) , m_visible(false) , m_texture(nullptr) , m_x(0.0f) , m_y(0.0f) , m_last_x(0.0f) , m_last_y(0.0f) , m_time(0) { // for now, use the main screen m_screen = screen_device_iterator(machine.root_device()).first(); } //------------------------------------------------- // render_crosshair - destructor //------------------------------------------------- render_crosshair::~render_crosshair() { m_machine.render().texture_free(m_texture); } //------------------------------------------------- // set_bitmap_name - change the bitmap name //------------------------------------------------- void render_crosshair::set_bitmap_name(const char *name) { // update bitmap if name has changed bool changed = name != m_name; m_name = name; if (changed) create_bitmap(); } //------------------------------------------------- // set_default_bitmap - reset to default bitmap //------------------------------------------------- void render_crosshair::set_default_bitmap() { // update bitmap if name has changed bool changed = !m_name.empty(); m_name.clear(); if (changed || m_bitmap == nullptr) create_bitmap(); } //------------------------------------------------- // create_bitmap - create the rendering // structures for the given player //------------------------------------------------- void render_crosshair::create_bitmap() { int x, y; rgb_t color = m_player < ARRAY_LENGTH(crosshair_colors) ? crosshair_colors[m_player] : rgb_t::white(); // if we have a bitmap and texture for this player, kill it if (m_bitmap == nullptr) { m_bitmap = std::make_unique(); m_texture = m_machine.render().texture_alloc(render_texture::hq_scale); } emu_file crossfile(m_machine.options().crosshair_path(), OPEN_FLAG_READ); if (!m_name.empty()) { // look for user specified file std::string filename = m_name + ".png"; render_load_png(*m_bitmap, crossfile, nullptr, filename.c_str()); } else { // look for default cross?.png in crsshair/game dir std::string filename = string_format("cross%d.png", m_player + 1); render_load_png(*m_bitmap, crossfile, m_machine.system().name, filename.c_str()); // look for default cross?.png in crsshair dir if (!m_bitmap->valid()) render_load_png(*m_bitmap, crossfile, nullptr, filename.c_str()); } /* if that didn't work, use the built-in one */ if (!m_bitmap->valid()) { /* allocate a blank bitmap to start with */ m_bitmap->allocate(CROSSHAIR_RAW_SIZE, CROSSHAIR_RAW_SIZE); m_bitmap->fill(rgb_t(0x00,0xff,0xff,0xff)); /* extract the raw source data to it */ for (y = 0; y < CROSSHAIR_RAW_SIZE / 2; y++) { /* assume it is mirrored vertically */ u32 *dest0 = &m_bitmap->pix32(y); u32 *dest1 = &m_bitmap->pix32(CROSSHAIR_RAW_SIZE - 1 - y); /* extract to two rows simultaneously */ for (x = 0; x < CROSSHAIR_RAW_SIZE; x++) if ((crosshair_raw_top[y * CROSSHAIR_RAW_ROWBYTES + x / 8] << (x % 8)) & 0x80) dest0[x] = dest1[x] = rgb_t(0xff,0x00,0x00,0x00) | color; } } /* reference the new bitmap */ m_texture->set_bitmap(*m_bitmap, m_bitmap->cliprect(), TEXFORMAT_ARGB32); } //------------------------------------------------- // animate - update the crosshair state //------------------------------------------------- void render_crosshair::animate(u16 auto_time) { // read all the port values if (m_used) m_machine.ioport().crosshair_position(m_player, m_x, m_y); // auto visibility if (m_mode == CROSSHAIR_VISIBILITY_AUTO) { if ((m_x != m_last_x) || (m_y != m_last_y)) { // crosshair has moved, keep crosshair visible m_visible = true; m_last_x = m_x; m_last_y = m_y; m_time = 0; } else { // see if the player has been motionless for time specified // slightly confusing formula, but the effect is: // auto_time = 0 makes the crosshair barely visible while moved // every increment in auto_time is about .2s at 60Hz if (m_time > auto_time * 12 + 2) // time exceeded so turn crosshair invisible m_visible = false; // increment player visibility time m_time++; } } } //------------------------------------------------- // draw - render the crosshair to the container //------------------------------------------------- void render_crosshair::draw(render_container &container, u8 fade) { // add a quad assuming a 4:3 screen (this is not perfect) container.add_quad(m_x - 0.03f, m_y - 0.04f, m_x + 0.03f, m_y + 0.04f, rgb_t(0xc0, fade, fade, fade), m_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } //************************************************************************** // CROSSHAIR MANAGER //************************************************************************** //------------------------------------------------- // crosshair_manager - constructor //------------------------------------------------- crosshair_manager::crosshair_manager(running_machine &machine) : m_machine(machine) , m_usage(false) , m_animation_counter(0) , m_auto_time(CROSSHAIR_VISIBILITY_AUTOTIME_DEFAULT) { /* request a callback upon exiting */ machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&crosshair_manager::exit, this)); for (int player = 0; player < MAX_PLAYERS; player++) m_crosshair[player] = std::make_unique(machine, player); /* determine who needs crosshairs */ for (auto &port : machine.ioport().ports()) for (ioport_field &field : port.second->fields()) if (field.crosshair_axis() != CROSSHAIR_AXIS_NONE) { int player = field.player(); assert(player < MAX_PLAYERS); /* mark as used and set the default visibility and mode */ m_usage = true; m_crosshair[player]->set_used(true); m_crosshair[player]->set_mode(CROSSHAIR_VISIBILITY_DEFAULT); m_crosshair[player]->set_visible(CROSSHAIR_VISIBILITY_DEFAULT != CROSSHAIR_VISIBILITY_OFF); m_crosshair[player]->set_default_bitmap(); } /* register callbacks for when we load/save configurations */ if (m_usage) machine.configuration().config_register("crosshairs", config_load_delegate(&crosshair_manager::config_load, this), config_save_delegate(&crosshair_manager::config_save, this)); /* register the animation callback */ screen_device *first_screen = screen_device_iterator(machine.root_device()).first(); if (first_screen != nullptr) first_screen->register_vblank_callback(vblank_state_delegate(&crosshair_manager::animate, this)); } /*------------------------------------------------- exit - free memory allocated for the crosshairs -------------------------------------------------*/ void crosshair_manager::exit() { /* free bitmaps and textures for each player */ for (int player = 0; player < MAX_PLAYERS; player++) m_crosshair[player] = nullptr; } /*------------------------------------------------- animate - animates the crosshair once a frame -------------------------------------------------*/ void crosshair_manager::animate(screen_device &device, bool vblank_state) { int player; /* only animate once per frame, when vblank_state is 1 */ if (!vblank_state) return; /* increment animation counter */ m_animation_counter += 0x08; /* compute a fade factor from the current animation value */ if (m_animation_counter < 0x80) m_fade = 0xa0 + (0x60 * ( m_animation_counter & 0x7f) / 0x80); else m_fade = 0xa0 + (0x60 * (~m_animation_counter & 0x7f) / 0x80); for (player = 0; player < MAX_PLAYERS; player++) m_crosshair[player]->animate(m_auto_time); } /*------------------------------------------------- render - render the crosshairs for the given screen -------------------------------------------------*/ void crosshair_manager::render(screen_device &screen) { int player; for (player = 0; player < MAX_PLAYERS; player++) { render_crosshair &crosshair = *m_crosshair[player]; // draw if visible and the right screen if (crosshair.is_visible() && ((crosshair.screen() == &screen) || (crosshair.screen() == CROSSHAIR_SCREEN_ALL))) crosshair.draw(screen.container(), m_fade); } } /*------------------------------------------------- config_load - read and apply data from the configuration file -------------------------------------------------*/ void crosshair_manager::config_load(config_type cfg_type, util::xml::data_node const *parentnode) { /* Note: crosshair_load() is only registered if croshairs are used */ /* we only care about game files */ if (cfg_type != config_type::GAME) return; /* might not have any data */ if (parentnode == nullptr) return; /* loop and get player crosshair info */ for (util::xml::data_node const *crosshairnode = parentnode->get_child("crosshair"); crosshairnode; crosshairnode = crosshairnode->get_next_sibling("crosshair")) { int const player = crosshairnode->get_attribute_int("player", -1); // check to make sure we have a valid player if (player >= 0 && player < MAX_PLAYERS) { // check if the player really uses a crosshair render_crosshair &crosshair = *m_crosshair[player]; if (crosshair.is_used()) { // get, check, and store visibility mode int const mode = crosshairnode->get_attribute_int("mode", CROSSHAIR_VISIBILITY_DEFAULT); if (mode >= CROSSHAIR_VISIBILITY_OFF && mode <= CROSSHAIR_VISIBILITY_AUTO) { crosshair.set_mode(u8(mode)); /* set visibility as specified by mode */ /* auto mode starts with visibility off */ crosshair.set_visible(mode == CROSSHAIR_VISIBILITY_ON); } // get and store crosshair pic name crosshair.set_bitmap_name(crosshairnode->get_attribute_string("pic", "")); } } } /* get, check, and store auto visibility time */ util::xml::data_node const *crosshairnode = parentnode->get_child("autotime"); if (crosshairnode) { int const auto_time = crosshairnode->get_attribute_int("val", CROSSHAIR_VISIBILITY_AUTOTIME_DEFAULT); if ((auto_time >= CROSSHAIR_VISIBILITY_AUTOTIME_MIN) && (auto_time <= CROSSHAIR_VISIBILITY_AUTOTIME_MAX)) m_auto_time = u8(auto_time); } } /*------------------------------------------------- config_save - save data to the configuration file -------------------------------------------------*/ void crosshair_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode) { /* Note: crosshair_save() is only registered if crosshairs are used */ /* we only care about game files */ if (cfg_type != config_type::GAME) return; for (int player = 0; player < MAX_PLAYERS; player++) { const render_crosshair &crosshair = *m_crosshair[player]; if (crosshair.is_used()) { /* create a node */ util::xml::data_node *const crosshairnode = parentnode->add_child("crosshair", nullptr); if (crosshairnode != nullptr) { bool changed = false; crosshairnode->set_attribute_int("player", player); if (crosshair.mode() != CROSSHAIR_VISIBILITY_DEFAULT) { crosshairnode->set_attribute_int("mode", crosshair.mode()); changed = true; } // only save graphic name if not the default if (*crosshair.bitmap_name() != '\0') { crosshairnode->set_attribute("pic", crosshair.bitmap_name()); changed = true; } /* if nothing changed, kill the node */ if (!changed) crosshairnode->delete_node(); } } } /* always store autotime so that it stays at the user value if it is needed */ if (m_auto_time != CROSSHAIR_VISIBILITY_AUTOTIME_DEFAULT) { /* create a node */ util::xml::data_node *const crosshairnode = parentnode->add_child("autotime", nullptr); if (crosshairnode != nullptr) crosshairnode->set_attribute_int("val", m_auto_time); } }