view src/editobj.cc @ 66:794b7bb40d7f

Initial work on cleaning up and improving the texture alignment functionality.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 26 Sep 2011 10:16:55 +0300
parents 2537585c2b93
children 2f1ecc1c5f72
line wrap: on
line source

/*
 *        editobj.cc
 *        Object editing routines.
 *        BW & RQ sometime in 1993 or 1994.
 */


/*
This file is part of Yadex.

Yadex incorporates code from DEU 5.21 that was put in the public domain in
1994 by Raphaël Quinet and Brendon Wyber.

The rest of Yadex is Copyright © 1997-2003 André Majorel and others.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.
*/


#include "yadex.h"
#include "_edit.h"
#include "dialog.h"
#include "editobj.h"
#include "entry.h"
#include "flats.h"
#include "game.h"
#include "gfx.h"
#include "levels.h"
#include "objects.h"
#include "objid.h"
#include "oldmenus.h"
#include "s_slice.h"
#include "s_swapf.h"
#include "selectn.h"
#include "t_spin.h"
#include "x_mirror.h"
#include "x_rotate.h"


/*
   ask for an object number and check for maximum valid number
   (this is just like InputIntegerValue, but with a different prompt)
*/

int InputObjectNumber(int x0, int y0, int objtype, int curobj)
{
    int val, key;
    char prompt[80];

    sprintf(prompt, "Enter a %s number between 0 and %d:",
            GetObjectTypeName(objtype), GetMaxObjectNum(objtype));
    if (x0 < 0)
        x0 = (ScrMaxX - 25 - 8 * strlen(prompt)) / 2;
    if (y0 < 0)
        y0 = (ScrMaxY - 55) / 2;
    DrawScreenBox3D(x0, y0, x0 + 25 + 8 * strlen(prompt), y0 + 55);
    set_colour(WHITE);
    DrawScreenText(x0 + 10, y0 + 8, prompt);
    val = curobj;
    while ((key
            =
            InputInteger(x0 + 10, y0 + 28, &val, 0,
                         GetMaxObjectNum(objtype))) != YK_RETURN
           && key != YK_ESC)
        Beep();
    return val;
}


/*
 *        input_objid - ask for an object number of the specified        type
 *
 *        If the user hit [Return], set objid.type to init.type
 *        and objid.num to whatever number the user entered. If
 *        the user hit [Esc], call nil() on objid.
 */
void input_objid(Objid & objid, const Objid & init, int x0, int y0)
{
    char prompt[80];

    sprintf(prompt, "Enter a %s number between 0 and %d:",
            GetObjectTypeName(init.type), GetMaxObjectNum(init.type));
    if (x0 < 0)
        x0 = (ScrMaxX - 25 - 8 * strlen(prompt)) / 2;
    if (y0 < 0)
        y0 = (ScrMaxY - 55) / 2;
    DrawScreenBox3D(x0, y0, x0 + 25 + 8 * strlen(prompt), y0 + 55);
    set_colour(WHITE);
    DrawScreenText(x0 + 10, y0 + 8, prompt);
    int num = init.num;
    int key;
    while ((key
            =
            InputInteger(x0 + 10, y0 + 28, &num, 0,
                         GetMaxObjectNum(init.type))) != YK_RETURN
           && key != YK_ESC)
        Beep();
    if (key == YK_ESC)
        objid.nil();
    else if (key == YK_RETURN)
    {
        objid.type = init.type;
        objid.num = num;
    }
    else
    {
        nf_bug("input_objid: bad key %d", (int) key);        // Can't happen
        objid.nil();
    }
}


/*
   ask for an object number and display a warning message
*/

int InputObjectXRef(int x0, int y0, int objtype, bool allownone, int curobj)
{
    const char *const msg1 = "Warning: modifying the cross-references";
    const char *const msg2 = "between some objects may crash the game.";
    char prompt[80];
    size_t maxlen = 0;
    int width;
    int height;

// Dimensions
    sprintf(prompt, "Enter a %s number between 0 and %d%c",
            GetObjectTypeName(objtype),
            GetMaxObjectNum(objtype), allownone ? ',' : ':');

    maxlen = 40;                // Why 40 ? -- AYM 2002-04-17

    if (strlen(prompt) > maxlen)
        maxlen = strlen(prompt);
    if (strlen(msg1) > maxlen)
        maxlen = strlen(msg1);
    if (strlen(msg2) > maxlen)
        maxlen = strlen(msg2);

    int ya = 0 + BOX_BORDER + WIDE_VSPACING;
    int yb = ya;
    if (allownone)
        yb += FONTH;
    int yc = yb + FONTH + WIDE_VSPACING;
// FIXME should query InputInteger() instead
    int yd =
        yc + 2 * HOLLOW_BORDER + 2 * NARROW_VSPACING + FONTH + WIDE_VSPACING;
    int ye = yd + FONTH;
    int yf = ye + FONTH + WIDE_VSPACING + BOX_BORDER;
    width = 2 * BOX_BORDER + 2 * WIDE_HSPACING + maxlen * FONTW;
    height = yf - 0;

// Position
    if (x0 < 0)
        x0 = (ScrMaxX - width) / 2;
    if (y0 < 0)
        y0 = (ScrMaxY - height) / 2;

    DrawScreenBox3D(x0, y0, x0 + width, y0 + height);
    set_colour(WHITE);
    int x = x0 + BOX_BORDER + WIDE_HSPACING;
    DrawScreenText(x, y0 + ya, prompt);
    if (allownone)
        DrawScreenText(x, y0 + yb, "or -1 for none:");
    set_colour(LIGHTRED);
    DrawScreenText(x, y0 + yd, msg1);
    DrawScreenText(x, y0 + ye, msg2);

    int val = curobj;
    int key;
    int min = allownone ? -1 : 0;
    int max = GetMaxObjectNum(objtype);
    while (key = InputInteger(x, y0 + yc, &val, min, max),
           key != YK_RETURN && key != YK_ESC)
        Beep();
    return val;
}



/*
   ask for two vertex numbers and check for maximum valid number
*/

bool Input2VertexNumbers(int x0, int y0, const char *prompt1, int *v1,
                         int *v2)
{
    int key;
    int maxlen, first;
    char prompt2[80];
    int text_x0;
    int text_y0;
    int entry1_x0;
    int entry1_y0;
    int entry2_x0;
    int entry2_y0;
// FIXME should let InputInteger() tell us
    const int entry_width =
        2 * HOLLOW_BORDER + 2 * NARROW_HSPACING + 7 * FONTW;
    const int entry_height = 2 * HOLLOW_BORDER + 2 * NARROW_VSPACING + FONTH;

    sprintf(prompt2, "Enter two numbers between 0 and %d:", NumVertices - 1);

    if (strlen(prompt1) > strlen(prompt2))
        maxlen = strlen(prompt1);
    else
        maxlen = strlen(prompt2);
    if (x0 < 0)
        x0 = (ScrMaxX - 25 - 8 * maxlen) / 2;
    if (y0 < 0)
        y0 = (ScrMaxY - 75) / 2;
    text_x0 = x0 + BOX_BORDER + WIDE_HSPACING;
    text_y0 = y0 + BOX_BORDER + WIDE_VSPACING;
    entry1_x0 = text_x0 + 13 * FONTW;
    entry1_y0 = text_y0 + 3 * FONTH - HOLLOW_BORDER - NARROW_VSPACING;
    entry2_x0 = entry1_x0;
    entry2_y0 = text_y0 + 5 * FONTH - HOLLOW_BORDER - NARROW_VSPACING;

    DrawScreenBox3D(x0, y0,
                    x0 + 2 * BOX_BORDER + 2 * WIDE_HSPACING
                    + y_max(entry_width + 13 * FONTW, maxlen * FONTW) - 1,
                    y0 + 2 * BOX_BORDER + 2 * WIDE_VSPACING + 6 * FONTH - 1);
    set_colour(WHITE);
    DrawScreenText(text_x0, text_y0, prompt1);
    set_colour(WINFG);
    DrawScreenText(text_x0, text_y0 + FONTH, prompt2);
    DrawScreenText(text_x0, text_y0 + 3 * FONTH, "Start vertex");
    DrawScreenText(text_x0, text_y0 + 5 * FONTH, "End vertex");

    first = 1;
    key = 0;
    for (;;)
    {
        DrawScreenBoxHollow(entry1_x0, entry1_y0,
                            entry1_x0 + entry_width - 1,
                            entry1_y0 + entry_height - 1, BLACK);
        set_colour(first ? WHITE : DARKGREY);
        DrawScreenText(entry1_x0 + HOLLOW_BORDER + NARROW_HSPACING,
                       entry1_y0 + HOLLOW_BORDER + NARROW_VSPACING, "%d",
                       *v1);

        DrawScreenBoxHollow(entry2_x0, entry2_y0,
                            entry2_x0 + entry_width - 1,
                            entry2_y0 + entry_height - 1, BLACK);
        set_colour(!first ? WHITE : DARKGREY);
        DrawScreenText(entry2_x0 + HOLLOW_BORDER + NARROW_HSPACING,
                       entry2_y0 + HOLLOW_BORDER + NARROW_VSPACING, "%d",
                       *v2);

        if (first)
            key = InputInteger(entry1_x0, entry1_y0, v1, 0, NumVertices - 1);
        else
            key = InputInteger(entry2_x0, entry2_y0, v2, 0, NumVertices - 1);
        if (key == YK_LEFT || key == YK_RIGHT || key == YK_TAB
            || key == YK_BACKTAB)
            first = !first;
        else if (key == YK_ESC)
            break;
        else if (key == YK_RETURN)
        {
            if (first)
                first = 0;
            else if (*v1 < 0 || *v1 >= NumVertices
                     || *v2 < 0 || *v2 >= NumVertices)
                Beep();
            else
                break;
        }
        else
            Beep();
    }
    return (key == YK_RETURN);
}



/*
   edit an object or a group of objects
*/

void EditObjectsInfo(int x0, int y0, int objtype, SelPtr obj)        /* SWAP! */
{
    char *menustr[3];
    int n, val;
    SelPtr cur;
    int subwin_y0;

    if (!obj)
        return;
    switch (objtype)
    {
    case OBJ_THINGS:
        ThingProperties(x0, y0, obj);
        break;

    case OBJ_VERTICES:
        for (n = 0; n < 3; n++)
            menustr[n] = (char *) GetMemory(60);
        sprintf(menustr[2], "Edit Vertex #%d", obj->objnum);
        sprintf(menustr[0], "Change X position (Current: %d)",
                Vertices[obj->objnum].x);
        sprintf(menustr[1], "Change Y position (Current: %d)",
                Vertices[obj->objnum].y);
#ifdef OLDMEN
        val = DisplayMenuArray(0, y0,
                               menustr[2], 2, NULL, menustr, NULL, NULL,
                               NULL);
#else
        val = vDisplayMenu(0, y0, menustr[2],
                           menustr[0], YK_, 0, menustr[1], YK_, 0, NULL);
#endif
        for (n = 0; n < 3; n++)
            FreeMemory(menustr[n]);
        subwin_y0 = y0 + BOX_BORDER + (2 + val) * FONTH;
        switch (val)
        {
        case 1:
            val = InputIntegerValue(x0 + 42, subwin_y0,
                                    y_min(MapMinX, -10000),
                                    y_max(MapMaxX, 10000),
                                    Vertices[obj->objnum].x);
            if (val != IIV_CANCEL)
            {
                n = val - Vertices[obj->objnum].x;
                for (cur = obj; cur; cur = cur->next)
                    Vertices[cur->objnum].x += n;
                MadeChanges = 1;
                MadeMapChanges = 1;
            }
            break;

        case 2:
            val = InputIntegerValue(x0 + 42, subwin_y0,
                                    y_min(MapMinY, -10000),
                                    y_max(MapMaxY, 10000),
                                    Vertices[obj->objnum].y);
            if (val != IIV_CANCEL)
            {
                n = val - Vertices[obj->objnum].y;
                for (cur = obj; cur; cur = cur->next)
                    Vertices[cur->objnum].y += n;
                MadeChanges = 1;
                MadeMapChanges = 1;
            }
            break;
        }
        break;

    case OBJ_LINEDEFS:
        LinedefProperties(x0, y0, obj);
        break;

    case OBJ_SECTORS:
        SectorProperties(x0, y0, obj);
        break;
    }
}


/*
   Yuck!  Dirty piece of code...
*/

bool Input2Numbers(int x0, int y0, const char *name1, const char *name2,
                   int v1max, int v2max, int *v1, int *v2)
{
    int key;
    int maxlen, first;
    bool ok;
    char prompt[80];
// FIXME copied from InputInteger()...
    int entry_width = 2 * HOLLOW_BORDER + 2 * NARROW_HSPACING + 7 * FONTW;
    int entry_height = 2 * HOLLOW_BORDER + 2 * NARROW_VSPACING + FONTH;

    y_snprintf(prompt, sizeof prompt, "Give the %s and %s for the object:",
               name1, name2);
    maxlen = strlen(prompt);

    int title_x0 = BOX_BORDER + FONTW;
    int title_y0 = BOX_BORDER + FONTH / 2;
    int label1_x0 = title_x0;
    int label1_y0 = title_y0 + 2 * FONTH;
    int label2_x0 = title_x0 + (strlen(name1) + 2) * FONTW;
    {
        int bound = label1_x0 + entry_width + int (FONTW);
        if (label2_x0 < bound)
            label2_x0 = bound;
    }
// FIXME Assuming the range is not longer than the name
    int label2_y0 = label1_y0;
    int entry1_out_x0 = label1_x0;
    int entry1_out_y0 = label1_y0 + 3 * FONTH / 2;
    int entry1_text_x0 = entry1_out_x0 + HOLLOW_BORDER + NARROW_HSPACING;
    int entry1_text_y0 = entry1_out_y0 + HOLLOW_BORDER + NARROW_VSPACING;
    int entry1_out_x1 = entry1_out_x0 + entry_width - 1;
    int entry1_out_y1 = entry1_out_y0 + entry_height - 1;
    int entry2_out_x0 = label2_x0;
    int entry2_out_y0 = label2_y0 + 3 * FONTH / 2;
    int entry2_text_x0 = entry2_out_x0 + HOLLOW_BORDER + NARROW_HSPACING;
    int entry2_text_y0 = entry2_out_y0 + HOLLOW_BORDER + NARROW_VSPACING;
    int entry2_out_x1 = entry2_out_x0 + entry_width - 1;
    int entry2_out_y1 = entry2_out_y0 + entry_height - 1;
    int range1_x0 = entry1_out_x0;
    int range1_y0 = entry1_out_y1 + FONTH / 2;
    int range2_x0 = entry2_out_x0;
    int range2_y0 = entry2_out_y1 + FONTH / 2;
    int window_x1 = entry2_out_x1 + FONTW + BOX_BORDER;
    int window_y1 = range1_y0 + 3 * FONTH / 2 + BOX_BORDER;
    {
        int bound = 2 * BOX_BORDER + (maxlen + 2) * int (FONTW);
        if (window_x1 < bound)
            window_x1 = bound;
    }

    if (x0 < 0)
        x0 = (ScrMaxX - window_x1) / 2;
    if (y0 < 0)
        y0 = (ScrMaxY - window_y1) / 2;

    DrawScreenBox3D(x0, y0, x0 + window_x1, y0 + window_y1);
    set_colour(WHITE);
    DrawScreenText(x0 + title_x0, y0 + title_x0, prompt);
    DrawScreenText(x0 + label1_x0, y0 + label1_y0, name1);
    DrawScreenText(x0 + label2_x0, y0 + label2_y0, name2);
    DrawScreenText(x0 + range1_x0, y0 + range1_y0, "(0-%d)", v1max);
    DrawScreenText(x0 + range2_x0, y0 + range2_y0, "(0-%d)", v2max);

    first = 1;
    key = 0;
    for (;;)
    {
        ok = true;
        DrawScreenBoxHollow(x0 + entry1_out_x0, y0 + entry1_out_y0,
                            x0 + entry1_out_x1, y0 + entry1_out_y1, BLACK);
        if (*v1 < 0 || *v1 > v1max)
        {
            set_colour(DARKGREY);
            ok = false;
        }
        else
            set_colour(LIGHTGREY);
        DrawScreenText(x0 + entry1_text_x0, y0 + entry1_text_y0, "%d", *v1);
        DrawScreenBoxHollow(x0 + entry2_out_x0, y0 + entry2_out_y0,
                            x0 + entry2_out_x1, y0 + entry2_out_y1, BLACK);
        if (*v2 < 0 || *v2 > v2max)
        {
            set_colour(DARKGREY);
            ok = false;
        }
        else
            set_colour(LIGHTGREY);
        DrawScreenText(x0 + entry2_text_x0, y0 + entry2_text_y0, "%d", *v2);
        if (first)
            key =
                InputInteger(x0 + entry1_out_x0, y0 + entry1_out_y0, v1, 0,
                             v1max);
        else
            key =
                InputInteger(x0 + entry2_out_x0, y0 + entry2_out_y0, v2, 0,
                             v2max);
        if (key == YK_LEFT || key == YK_RIGHT || key == YK_TAB
            || key == YK_BACKTAB)
            first = !first;
        else if (key == YK_ESC)
            break;
        else if (key == YK_RETURN)
        {
            if (first)
                first = 0;
            else if (ok)
                break;
            else
                Beep();
        }
        else
            Beep();
    }
    return (key == YK_RETURN);
}



/*
   insert a standard object at given position
*/

void InsertStandardObject(int xpos, int ypos, int choice)        /* SWAP! */
{
    int sector;
    int n;
    int a, b;

/* are we inside a Sector? */
    Objid o;
    GetCurObject(o, OBJ_SECTORS, xpos, ypos);
    sector = o.num;

/* !!!! Should also check for overlapping objects (sectors) !!!! */
    switch (choice)
    {
    case 1:
        a = 256;
        b = 128;
        if (Input2Numbers(-1, -1, "Width", "Height", 2000, 2000, &a, &b))
        {
            if (a < 8)
                a = 8;
            if (b < 8)
                b = 8;
            xpos = xpos - a / 2;
            ypos = ypos - b / 2;
            InsertObject(OBJ_VERTICES, -1, xpos, ypos);
            InsertObject(OBJ_VERTICES, -1, xpos + a, ypos);
            InsertObject(OBJ_VERTICES, -1, xpos + a, ypos + b);
            InsertObject(OBJ_VERTICES, -1, xpos, ypos + b);
            if (sector < 0)
                InsertObject(OBJ_SECTORS, -1, 0, 0);
            for (n = 0; n < 4; n++)
            {
                InsertObject(OBJ_LINEDEFS, -1, 0, 0);
                LineDefs[NumLineDefs - 1].sidedef1 = NumSideDefs;
                InsertObject(OBJ_SIDEDEFS, -1, 0, 0);
                if (sector >= 0)
                    SideDefs[NumSideDefs - 1].sector = sector;
            }
            if (sector >= 0)
            {
                LineDefs[NumLineDefs - 4].start = NumVertices - 4;
                LineDefs[NumLineDefs - 4].end = NumVertices - 3;
                LineDefs[NumLineDefs - 3].start = NumVertices - 3;
                LineDefs[NumLineDefs - 3].end = NumVertices - 2;
                LineDefs[NumLineDefs - 2].start = NumVertices - 2;
                LineDefs[NumLineDefs - 2].end = NumVertices - 1;
                LineDefs[NumLineDefs - 1].start = NumVertices - 1;
                LineDefs[NumLineDefs - 1].end = NumVertices - 4;
            }
            else
            {
                LineDefs[NumLineDefs - 4].start = NumVertices - 1;
                LineDefs[NumLineDefs - 4].end = NumVertices - 2;
                LineDefs[NumLineDefs - 3].start = NumVertices - 2;
                LineDefs[NumLineDefs - 3].end = NumVertices - 3;
                LineDefs[NumLineDefs - 2].start = NumVertices - 3;
                LineDefs[NumLineDefs - 2].end = NumVertices - 4;
                LineDefs[NumLineDefs - 1].start = NumVertices - 4;
                LineDefs[NumLineDefs - 1].end = NumVertices - 1;
            }
        }
        break;
    case 2:
        a = 8;
        b = 128;
        if (Input2Numbers
            (-1, -1, "Number of sides", "Radius", 32, 2000, &a, &b))
        {
            if (a < 3)
                a = 3;
            if (b < 8)
                b = 8;
            InsertPolygonVertices(xpos, ypos, a, b);
            if (sector < 0)
                InsertObject(OBJ_SECTORS, -1, 0, 0);
            for (n = 0; n < a; n++)
            {
                InsertObject(OBJ_LINEDEFS, -1, 0, 0);
                LineDefs[NumLineDefs - 1].sidedef1 = NumSideDefs;
                InsertObject(OBJ_SIDEDEFS, -1, 0, 0);
                if (sector >= 0)
                    SideDefs[NumSideDefs - 1].sector = sector;
            }
            if (sector >= 0)
            {
                LineDefs[NumLineDefs - 1].start = NumVertices - 1;
                LineDefs[NumLineDefs - 1].end = NumVertices - a;
                for (n = 2; n <= a; n++)
                {
                    LineDefs[NumLineDefs - n].start = NumVertices - n;
                    LineDefs[NumLineDefs - n].end = NumVertices - n + 1;
                }
            }
            else
            {
                LineDefs[NumLineDefs - 1].start = NumVertices - a;
                LineDefs[NumLineDefs - 1].end = NumVertices - 1;
                for (n = 2; n <= a; n++)
                {
                    LineDefs[NumLineDefs - n].start = NumVertices - n + 1;
                    LineDefs[NumLineDefs - n].end = NumVertices - n;
                }
            }
        }
        break;
    case 3:
        /*
           a = 6;
           b = 16;
           if (Input2Numbers (-1, -1, "Number of steps", "Step height", 32, 48, &a, &b))
           {
           if (a < 2)
           a = 2;
           n = Sectors[sector].ceilh;
           h = Sectors[sector].floorh;
           if (a * b < n - h)
           {
           Beep ();
           Notify (-1, -1, "The stairs are too high for this Sector", 0);
           return;
           }
           xpos = xpos - 32;
           ypos = ypos - 32 * a;
           for (n = 0; n < a; n++)
           {
           InsertObject (OBJ_VERTICES, -1, xpos, ypos);
           InsertObject (OBJ_VERTICES, -1, xpos + 64, ypos);
           InsertObject (OBJ_VERTICES, -1, xpos + 64, ypos + 64);
           InsertObject (OBJ_VERTICES, -1, xpos, ypos + 64);
           ypos += 64;
           InsertObject (OBJ_SECTORS, sector, 0, 0);
           h += b;
           Sectors[NumSectors - 1].floorh = h;

           InsertObject (OBJ_LINEDEFS, -1, 0, 0);
           LineDefs[NumLineDefs - 1].sidedef1 = NumSideDefs;
           LineDefs[NumLineDefs - 1].sidedef2 = NumSideDefs + 1;
           InsertObject (OBJ_SIDEDEFS, -1, 0, 0);
           SideDefs[NumSideDefs - 1].sector = sector;
           InsertObject (OBJ_SIDEDEFS, -1, 0, 0);

           LineDefs[NumLineDefs - 4].start = NumVertices - 4;
           LineDefs[NumLineDefs - 4].end = NumVertices - 3;
           LineDefs[NumLineDefs - 3].start = NumVertices - 3;
           LineDefs[NumLineDefs - 3].end = NumVertices - 2;
           LineDefs[NumLineDefs - 2].start = NumVertices - 2;
           LineDefs[NumLineDefs - 2].end = NumVertices - 1;
           LineDefs[NumLineDefs - 1].start = NumVertices - 1;
           LineDefs[NumLineDefs - 1].end = NumVertices - 4;
           }
           }
           break;
         */
    case 4:
        NotImplemented();
        break;
    }
}

static void AlignTexturesMenu(int xc, int yc, int mode, SelPtr *list)
{
    int opt_sdef;
    bool opt_offset = false, opt_check = false;

    switch (vDisplayMenu(xc, yc,
    mode ? "Aligning textures (Y offset) :" : "Aligning textures (X offset) :",

    " Sidedef 1, Check for identical textures.     ", YK_, 0,
    " Sidedef 1, As above, but with initial offset.", YK_, 0,
    " Sidedef 1, No texture checking.              ", YK_, 0,
    " Sidedef 1, As above, but with initial offset.", YK_, 0,
    " Sidedef 2, Check for identical textures.     ", YK_, 0,
    " Sidedef 2, As above, but with initial offset.", YK_, 0,
    " Sidedef 2, No texture checking.              ", YK_, 0,
    " Sidedef 2, As above, but with initial offset.", YK_, 0,
    NULL))
    {
    case 1:                        /* Sidedef 1 with checking for same textures   */
        opt_sdef = 1;
        opt_check = true;
        opt_offset = false;
        break;

    case 2:                        /* Sidedef 1 as above, but with inital offset  */
        opt_sdef = 1;
        opt_check = true;
        opt_offset = true;
        break;

    case 3:                        /* Sidedef 1 regardless of same textures       */
        opt_sdef = 1;
        opt_check = false;
        opt_offset = false;
        break;

    case 4:                        /* Sidedef 1 as above, but with inital offset  */
        opt_sdef = 1;
        opt_check = false;
        opt_offset = true;
        break;

    case 5:                        /* Sidedef 2 with checking for same textures   */
        opt_sdef = 2;
        opt_check = true;
        opt_offset = false;
        break;

    case 6:                        /* Sidedef 2 as above, but with initial offset */
        opt_sdef = 2;
        opt_check = true;
        opt_offset = true;
        break;

    case 7:                        /* Sidedef 2 regardless of same textures       */
        opt_sdef = 2;
        opt_check = false;
        opt_offset = false;
        break;

    case 8:                        /* Sidedef 2 as above, but with initial offset */
        opt_sdef = 2;
        opt_check = false;
        opt_offset = true;
        break;

    default:
        return;
    }

    /* Select all sidedefs */
    SelPtr sdlist = NULL, cur;
    for (cur = *list; cur; cur = cur->next)
    {
        if (opt_sdef == 1 && LineDefs[cur->objnum].sidedef1 >= 0)
            SelectObject(&sdlist, LineDefs[cur->objnum].sidedef1);

        if (opt_sdef == 2 && LineDefs[cur->objnum].sidedef2 >= 0)
            SelectObject(&sdlist, LineDefs[cur->objnum].sidedef2);
    }

    if (mode == 0)
        /* Align the textures along the X axis (width) */
        AlignTexturesX(&sdlist, opt_sdef, opt_check, opt_offset, -1);
    else
        /* Align the textures along the Y axis (height) */
        AlignTexturesY(&sdlist, opt_sdef, opt_check);
}


/*
 * Menu of miscellaneous operations
 */
void MiscOperations(int objtype, SelPtr * list, int val)        /* SWAP! */
{
    char msg[80];
    int angle, scale;

    if (val > 1 && !*list)
    {
        Beep();
        sprintf(msg, "You must select at least one %s",
                GetObjectTypeName(objtype));
        Notify(-1, -1, msg, 0);
        return;
    }

/* I think this switch statement deserves a prize for "worst
   gratuitous obfuscation" or something. -- AYM 2000-11-07 */
    switch (val)
    {
    case 1:
        // * -> First free tag number
        sprintf(msg, "First free tag number: %d", FindFreeTag());
        Notify(-1, -1, msg, 0);
        break;

    case 2:
        // * -> Rotate and scale
        if ((objtype == OBJ_THINGS
             || objtype == OBJ_VERTICES) && !(*list)->next)
        {
            Beep();
            sprintf(msg, "You must select more than one %s",
                    GetObjectTypeName(objtype));
            Notify(-1, -1, msg, 0);
            return;
        }
        angle = 0;
        scale = 100;
        if (Input2Numbers(-1, -1, "rotation angle (°)", "scale (%)",
                          360, 1000, &angle, &scale))
            RotateAndScaleObjects(objtype, *list, (double) angle * 0.0174533,
                                  (double) scale * 0.01);
        break;

    case 3:
        // Linedef -> Split
        if (objtype == OBJ_LINEDEFS)
        {
            SplitLineDefs(*list);
        }
        // Sector -> Make door from sector
        else if (objtype == OBJ_SECTORS)
        {
            if ((*list)->next)
            {
                Beep();
                Notify(-1, -1, "You must select exactly one sector", 0);
            }
            else
            {
                MakeDoorFromSector((*list)->objnum);
            }
        }
        // Thing -> Spin 45° clockwise
        else if (objtype == OBJ_THINGS)
        {
            spin_things(*list, -45);
        }
        // Vertex -> Delete and join linedefs
        else if (objtype == OBJ_VERTICES)
        {
            DeleteVerticesJoinLineDefs(*list);
            ForgetSelection(list);
        }
        break;

    case 4:
        // Linedef -> Split linedefs and sector
        if (objtype == OBJ_LINEDEFS)
        {
            if (!(*list)->next || (*list)->next->next)
            {
                Beep();
                Notify(-1, -1, "You must select exactly two linedefs", 0);
            }
            else
            {
                SplitLineDefsAndSector((*list)->next->objnum,
                                       (*list)->objnum);
                ForgetSelection(list);
            }
        }
        // Sector -> Make lift from sector
        else if (objtype == OBJ_SECTORS)
        {
            if ((*list)->next)
            {
                Beep();
                Notify(-1, -1, "You must select exactly one Sector", 0);
            }
            else
            {
                MakeLiftFromSector((*list)->objnum);
            }
        }
        // Thing -> Spin 45° counter-clockwise
        else if (objtype == OBJ_THINGS)
            spin_things(*list, 45);
        // Vertex -> Merge
        else if (objtype == OBJ_VERTICES)
        {
            MergeVertices(list);
        }
        break;

    case 5:
        // Linedef -> Delete linedefs and join sectors
        if (objtype == OBJ_LINEDEFS)
        {
            DeleteLineDefsJoinSectors(list);
        }
        // Sector -> Distribute sector floor heights
        else if (objtype == OBJ_SECTORS)
        {
            if (!(*list)->next || !(*list)->next->next)
            {
                Beep();
                Notify(-1, -1, "You must select three or more sectors", 0);
            }
            else
            {
                DistributeSectorFloors(*list);
            }
        }
        // Thing -> Mirror horizontally
        else if (objtype == OBJ_THINGS)
        {
            flip_mirror(*list, OBJ_THINGS, 'm');
        }
        // Vertex -> Add linedef and split sector
        else if (objtype == OBJ_VERTICES)
        {
            if (!(*list)->next || (*list)->next->next)
            {
                Beep();
                Notify(-1, -1, "You must select exactly two vertices", 0);
            }
            else
            {
                SplitSector((*list)->next->objnum, (*list)->objnum);
                ForgetSelection(list);
            }
        }
        break;

    case 6:
        // Linedef -> Flip
        if (objtype == OBJ_LINEDEFS)
        {
            FlipLineDefs(*list, 1);
        }
        // Sector -> Distribute ceiling heights
        else if (objtype == OBJ_SECTORS)
        {
            if (!(*list)->next || !(*list)->next->next)
            {
                Beep();
                Notify(-1, -1, "You must select three or more sectors", 0);
            }
            else
            {
                DistributeSectorCeilings(*list);
            }
        }
        // Things -> Mirror vertically
        else if (objtype == OBJ_THINGS)
        {
            flip_mirror(*list, OBJ_THINGS, 'f');
        }
        // Vertex -> Mirror horizontally
        else if (objtype == OBJ_VERTICES)
        {
            flip_mirror(*list, OBJ_VERTICES, 'm');
        }
        break;

    case 7:
        // Linedefs -> Swap sidedefs
        if (objtype == OBJ_LINEDEFS)
        {
            if (Expert
                || blindly_swap_sidedefs
                || Confirm(-1, -1,
                           "Warning: the sector references are also swapped",
                           "You may get strange results if you don't know what you are doing..."))
                FlipLineDefs(*list, 0);
        }
        // Sectors -> Raise or lower
        else if (objtype == OBJ_SECTORS)
        {
            RaiseOrLowerSectors(*list);
        }
        // Vertices -> Mirror vertically
        else if (objtype == OBJ_VERTICES)
        {
            flip_mirror(*list, OBJ_VERTICES, 'f');
        }
        break;

    case 8:
        // Linedef ->  Align textures vertically
        if (objtype == OBJ_LINEDEFS)
        {
            AlignTexturesMenu(250, 110, 1, list);
        }
        // Sector -> Brighten or darken
        else if (objtype == OBJ_SECTORS)
        {
            BrightenOrDarkenSectors(*list);
        }
        break;

    case 9:
        // Linedef -> Align texture horizontally
        if (objtype == OBJ_LINEDEFS)
        {
            AlignTexturesMenu(250, 110, 0, list);
        }
        // Sector -> Unlink room
        else if (objtype == OBJ_SECTORS)
        {
            NotImplemented();        // FIXME
            break;
        }
        break;

    case 10:
        // Linedef -> Make linedef single-sided
        if (objtype == OBJ_LINEDEFS)
        {
            SelPtr cur;
            for (cur = *list; cur; cur = cur->next)
            {
                struct LineDef *l = LineDefs + cur->objnum;
                l->sidedef2 = -1;        /* remove ref. to 2nd SD */
                l->flags &= ~0x04;        /* clear "2S" bit */
                l->flags |= 0x01;        /* set "Im" bit */

                if (is_sidedef(l->sidedef1))
                {
                    struct SideDef *s = SideDefs + l->sidedef1;
                    strcpy(s->tex1, "-");
                    strcpy(s->tex2, "-");
                    strcpy(s->tex3, default_middle_texture);
                }
                /* Don't delete the 2nd sidedef, it could be used
                   by another linedef. And if it isn't, the next
                   cross-references check will delete it anyway. */
            }
        }
        // Sector -> Mirror horizontally
        else if (objtype == OBJ_SECTORS)
        {
            flip_mirror(*list, OBJ_SECTORS, 'm');
        }
        break;

    case 11:
        // Linedef -> Make rectangular nook
        if (objtype == OBJ_LINEDEFS)
            MakeRectangularNook(*list, 32, 16, 0);
        // Sector -> Mirror vertically
        else if (objtype == OBJ_SECTORS)
        {
            flip_mirror(*list, OBJ_SECTORS, 'f');
        }
        break;

    case 12:
        // Linedef -> Make rectangular boss
        if (objtype == OBJ_LINEDEFS)
            MakeRectangularNook(*list, 32, 16, 1);
        // Sector -> Swap flats
        else if (objtype == OBJ_SECTORS)
            swap_flats(*list);
        break;

    case 13:
        // Linedef -> Set length (1st vertex)
        if (objtype == OBJ_LINEDEFS)
        {
            static int length = 24;
            length = InputIntegerValue(-1, -1, -10000, 10000, length);
            if (length != IIV_CANCEL)
                SetLinedefLength(*list, length, 0);
        }
        break;

    case 14:
        // Linedef -> Set length (2nd vertex)
        if (objtype == OBJ_LINEDEFS)
        {
            static int length = 24;
            length = InputIntegerValue(-1, -1, -10000, 10000, length);
            if (length != IIV_CANCEL)
                SetLinedefLength(*list, length, 1);
        }
        break;

    case 15:
        // Linedef -> Unlink 1st sidedef
        if (objtype == OBJ_LINEDEFS)
            unlink_sidedef(*list, 1, 0);
        break;

    case 16:
        // Linedef -> Unlink 2nd sidedef
        if (objtype == OBJ_LINEDEFS)
            unlink_sidedef(*list, 0, 1);
        break;

    case 17:
        // Linedef -> Mirror horizontally
        flip_mirror(*list, OBJ_LINEDEFS, 'm');
        break;

    case 18:
        // Linedef -> Mirror vertically
        flip_mirror(*list, OBJ_LINEDEFS, 'f');
        break;

    case 19:
        // Linedef -> Cut a slice out of a sector
        if (objtype == OBJ_LINEDEFS)
        {
            if (!(*list)->next || (*list)->next->next)
            {
                Beep();
                Notify(-1, -1, "You must select exactly two linedefs", 0);
            }
            else
            {
                sector_slice((*list)->next->objnum, (*list)->objnum);
                ForgetSelection(list);
            }
        }
        break;
    }
}