Areas can now be edited in the map editor. Areas can now be used for defining the locations a given native nation can have settlements (when generating settlements semi-randomly).

This commit is contained in:
Stian Grenborgen 2024-01-13 22:00:44 +01:00
parent c6ed870630
commit 06dcf01df9
19 changed files with 690 additions and 250 deletions

View File

@ -78,6 +78,7 @@ image.icon.coin=resources/images/misc/coin.png
image.icon.Colopedia.openSection=resources/images/ui/open_section_120pct.png
image.icon.Colopedia.closedSection=resources/images/ui/closed_section_125pct.png
image.icon.Colopedia.idea=resources/images/ui/idea.png
image.icon.mapEditor.selectArea=resources/images/ui/select-area.png
# User Interface Images
image.flavor.Declaration=resources/images/ui/doi.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -392,6 +392,7 @@ loadAction.accelerator=L
loadAction.name=Load
mapControlsAction.accelerator=control M
mapControlsAction.name=Map Controls
mapEditorToolboxPanelAction.name=Toolbox
mapEditorTransformPanelAction.name=Tile Transform
mapEditorAction.name=Map Editor
mapGeneratorOptionsAction.accelerator=shift F12
@ -3611,7 +3612,11 @@ loadingSavegameDialog.serverName=Server Name:
loadingSavegameDialog.singlePlayer=Single player
loadingSavegameDialog.name=Loading Save game
# Map Editor
mapEditor.loadedWithMods=You have started the map editor with active mods that contain specification changes. Please note that the same mods need to be manually activated by every user that loads maps that are using anything new from a mod.\n\nYou can go back to the main menu and deactivate any mods through the Preferences if this was not intentional.
mapEditor.chooseAreaModificationPanel.title=Choose Area
mapEditor.toolBoxPanel.title=Toolbox
mapEditor.tool.paintBrush.name=Paint
# MapEditorTransformPanel
mapEditorTransformPanel.title=Tile Transform
@ -3621,6 +3626,7 @@ mapEditorTransformPanel.minorRiver=Minor river
mapEditorTransformPanel.changeRiverConnections=Add/remove river connections
mapEditorTransformPanel.setRiverStyle=Set river style
mapEditorTransformPanel.resource=Change/remove resource
mapEditorTransformPanel.selectArea=Add/remove tiles from area
# MapGeneratorOptionsDialog
freecol.map.L_America_JsTheDude=<html>L America<br><font size="-2">By JsTheDude</font></html>

View File

@ -41,6 +41,7 @@ import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.io.FreeColDirectories;
import net.sf.freecol.common.io.FreeColModFile;
import net.sf.freecol.common.io.FreeColSavegameFile;
import net.sf.freecol.common.model.Area;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Nation;
@ -68,11 +69,15 @@ public final class MapEditorController extends FreeColClientHolder {
/** Map height in MapGeneratorOptionsDialog. */
private static final int MINI_MAP_THUMBNAIL_FINAL_HEIGHT = 64;
private MapEditorTool currentTool = MapEditorTool.PAINTBRUSH;
/**
* The transform that should be applied to a {@code Tile}
* that is clicked on the map.
*/
private MapTransform currentMapTransform = null;
private Area currentArea = null;
/**
@ -213,6 +218,18 @@ public final class MapEditorController extends FreeColClientHolder {
public MapTransform getMapTransform() {
return currentMapTransform;
}
public MapEditorTool getCurrentTool() {
return currentTool;
}
public void setCurrentArea(Area currentArea) {
this.currentArea = currentArea;
}
public Area getCurrentArea() {
return currentArea;
}
/**
* Transforms the given {@code Tile} using the
@ -330,6 +347,7 @@ public final class MapEditorController extends FreeColClientHolder {
Specification spec = getDefaultSpecification();
Game game = FreeColServer.readGame(new FreeColSavegameFile(theFile),
spec, freeColServer);
game.generateDefaultAreas();
fcc.setGame(game);
requireNativeNations(game);
SwingUtilities.invokeLater(() -> {

View File

@ -0,0 +1,42 @@
/**
* Copyright (C) 2002-2024 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol 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.
*
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.control;
/**
* Possible tools to use in the map editor.
*/
public enum MapEditorTool {
//CURSOR("mapEditor.tool.cursor"),
//SELECTION("mapEditor.tool.selection"),
PAINTBRUSH("mapEditor.tool.paintBrush"),
//FILL("mapEditor.tool.fill"),
;
private final String id;
private MapEditorTool(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@ -75,6 +75,7 @@ import net.sf.freecol.client.gui.panel.FreeColPanel;
import net.sf.freecol.client.gui.panel.InfoPanel;
import net.sf.freecol.client.gui.panel.MainPanel;
import net.sf.freecol.client.gui.panel.MapControls;
import net.sf.freecol.client.gui.panel.MapEditorToolboxPanel;
import net.sf.freecol.client.gui.panel.MapEditorTransformPanel;
import net.sf.freecol.client.gui.panel.MiniMapFreeColPanel;
import net.sf.freecol.client.gui.panel.Utility;
@ -693,6 +694,10 @@ public final class Canvas extends JDesktopPane {
x = ((getWidth() - width) * 3) / 4;
y = (getHeight() - height) / 2;
break;
case LEFT:
x = 0;
y = (getHeight() - height) / 2;
break;
case LOWER_LEFT:
x = 0;
y = getHeight() - height;
@ -705,6 +710,10 @@ public final class Canvas extends JDesktopPane {
x = getWidth() - width;
y = 0;
break;
case RIGHT:
x = getWidth() - width;
y = (getHeight() - height) / 2;
break;
case UPPER_LEFT:
case ORIGIN:
x = y = 0;
@ -1316,7 +1325,7 @@ public final class Canvas extends JDesktopPane {
dialogRemove(dialog);
return response;
}
/**
* Displays a {@code FreeColPanel} at a generalized position.
*
@ -1327,10 +1336,29 @@ public final class Canvas extends JDesktopPane {
* @return The panel.
*/
public FreeColPanel showFreeColPanel(FreeColPanel panel,
PopupPosition popupPosition,
boolean resizable) {
PopupPosition popupPosition,
boolean resizable) {
return showFreeColPanel(panel, false, popupPosition, resizable);
}
/**
* Displays a {@code FreeColPanel} at a generalized position.
*
* @param panel {@code FreeColPanel}, panel to show
* @param toolBox Should be set to true if the resulting frame is
* used as a toolbox (that is: it should not be counted as a
* frame).
* @param popupPosition {@code PopupPosition} The generalized
* position to place the panel.
* @param resizable Should the panel be resizable?
* @return The panel.
*/
public FreeColPanel showFreeColPanel(FreeColPanel panel,
boolean toolBox,
PopupPosition popupPosition,
boolean resizable) {
repaint();
addAsFrame(panel, false, popupPosition, resizable);
addAsFrame(panel, toolBox, popupPosition, resizable);
panel.requestFocus();
freeColClient.getActionManager().update();
return panel;
@ -1666,7 +1694,15 @@ public final class Canvas extends JDesktopPane {
addAsFrame(panel, true, null, true);
repaint();
}
/**
* Display the map editor toolbox panel.
*/
public void showMapEditorToolboxPanel() {
final MapEditorToolboxPanel panel = new MapEditorToolboxPanel(this.freeColClient);
addAsFrame(panel, true, null, true);
repaint();
}
// Override JComponent
public void paintJustTheMapImmediately() {

View File

@ -41,6 +41,7 @@ import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.FreeColClientHolder;
import net.sf.freecol.client.control.MapTransform;
import net.sf.freecol.client.gui.SwingGUI.PopupPosition;
import net.sf.freecol.client.gui.dialog.FreeColDialog;
import net.sf.freecol.client.gui.dialog.Parameters;
import net.sf.freecol.client.gui.mapviewer.MapAsyncPainter;
@ -1158,6 +1159,18 @@ public class GUI extends FreeColClientHolder {
*/
public void startMapEditorGUI() {}
/**
* Shows the map editor toolbox panel.
*/
public void enableEditorToolboxPanel(boolean shouldDisplayPanel) {}
/**
* Checks if the map editor toolbox panel is being displayed.
*/
public boolean isShowingMapEditorToolboxPanel() {
return false;
}
/**
* Shows the map editor transform panel.
*/
@ -1961,6 +1974,19 @@ public class GUI extends FreeColClientHolder {
* @return The panel shown.
*/
public FreeColPanel showColonyPanel(Colony colony, Unit unit) { return null; }
/**
* Displays a {@code FreeColPanel} at a generalized position.
*
* @param panel {@code FreeColPanel}, panel to show
* @param toolBox Should be set to true if the resulting frame is
* used as a toolbox (that is: it should not be counted as a
* frame).
* @param popupPosition {@code PopupPosition} The generalized
* position to place the panel.
* @param resizable Should the panel be resizable?
*/
public void showFreeColPanel(FreeColPanel panel, boolean toolBox, PopupPosition popupPosition, boolean resizable) { }
/**
* Show a colopedia panel.

View File

@ -42,7 +42,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -60,7 +59,7 @@ import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.MapTransform;
import net.sf.freecol.client.control.SoundController;
import net.sf.freecol.client.gui.action.MapEditorTransformPanelAction;
import net.sf.freecol.client.gui.SwingGUI.PopupPosition;
import net.sf.freecol.client.gui.animation.Animation;
import net.sf.freecol.client.gui.animation.Animations;
// Special dialogs and panels
@ -72,13 +71,14 @@ import net.sf.freecol.client.gui.mapviewer.MapAsyncPainter;
import net.sf.freecol.client.gui.mapviewer.MapViewer;
import net.sf.freecol.client.gui.mapviewer.MapViewerState;
import net.sf.freecol.client.gui.mapviewer.TileViewer;
import net.sf.freecol.client.gui.panel.BuildQueuePanel;
import net.sf.freecol.client.gui.panel.ColonyPanel;
import net.sf.freecol.client.gui.panel.CornerMapControls;
import net.sf.freecol.client.gui.panel.ErrorPanel;
import net.sf.freecol.client.gui.panel.FreeColImageBorder;
import net.sf.freecol.client.gui.panel.FreeColPanel;
import net.sf.freecol.client.gui.panel.InformationPanel;
import net.sf.freecol.client.gui.panel.MapControls;
import net.sf.freecol.client.gui.panel.MapEditorToolboxPanel;
import net.sf.freecol.client.gui.panel.MapEditorTransformPanel;
import net.sf.freecol.client.gui.panel.PurchasePanel;
import net.sf.freecol.client.gui.panel.RecruitPanel;
@ -126,7 +126,6 @@ import net.sf.freecol.common.option.LanguageOption;
import net.sf.freecol.common.option.LanguageOption.Language;
import net.sf.freecol.common.option.Option;
import net.sf.freecol.common.option.OptionGroup;
import net.sf.freecol.common.resources.AudioResource;
import net.sf.freecol.common.resources.ImageCache;
import net.sf.freecol.common.resources.ImageResource;
import net.sf.freecol.common.resources.ResourceManager;
@ -149,6 +148,8 @@ public class SwingGUI extends GUI {
LOWER_RIGHT,
UPPER_LEFT,
UPPER_RIGHT,
LEFT,
RIGHT,
/**
* Places centered even this means overlapping components.
@ -920,11 +921,33 @@ public class SwingGUI extends GUI {
resetMapZoom(); // Reset zoom to the default
mapViewer.getMapViewerState().setActiveUnit(null);
this.canvas.startMapEditorGUI();
enableEditorToolboxPanel(getGame() != null && getGame().getMap() != null);
enableEditorTransformPanel(getGame() != null && getGame().getMap() != null);
enableMapControls(getGame() != null && getGame().getMap() != null && getClientOptions().getBoolean(ClientOptions.DISPLAY_MAP_CONTROLS));
updateMenuBar();
}
/**
* {@inheritDoc}
*/
@Override
public void enableEditorToolboxPanel(boolean shouldDisplayPanel) {
final MapEditorToolboxPanel panel = this.canvas.getExistingFreeColPanel(MapEditorToolboxPanel.class);
if (shouldDisplayPanel && panel == null) {
this.canvas.showMapEditorToolboxPanel();
} else if (!shouldDisplayPanel && panel != null) {
this.canvas.removeFromCanvas(panel);
} // else: ignore.
}
/**
* {@inheritDoc}
*/
@Override
public boolean isShowingMapEditorToolboxPanel() {
return this.canvas.isAddedAsPanelFrameOrIcon(MapEditorToolboxPanel.class);
}
/**
* {@inheritDoc}
*/
@ -2095,6 +2118,18 @@ public class SwingGUI extends GUI {
if (unit != null) panel.setSelectedUnit(unit);
return panel;
}
/**
* {@inheritDoc}
*/
@Override
public void showFreeColPanel(FreeColPanel panel, boolean toolBox, PopupPosition popupPosition, boolean resizable) {
if (canvas.isAddedAsPanelFrameOrIcon(panel)) {
panel.requestFocus();
} else {
canvas.showFreeColPanel(panel, toolBox, popupPosition, resizable);
}
}
/**
* {@inheritDoc}

View File

@ -119,6 +119,7 @@ public class ActionManager extends OptionGroup {
add(new LoadAction(freeColClient));
add(new MapControlsAction(freeColClient));
add(new MapEditorAction(freeColClient));
add(new MapEditorToolboxPanelAction(freeColClient));
add(new MapEditorTransformPanelAction(freeColClient));
add(new MiniMapToggleViewAction(freeColClient));
add(new MiniMapToggleViewAction(freeColClient, true));

View File

@ -0,0 +1,68 @@
/**
* Copyright (C) 2002-2022 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol 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.
*
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.gui.action;
import java.awt.event.ActionEvent;
import javax.swing.AbstractButton;
import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
/**
* An action for displaying the map editor toolbox.
*/
public class MapEditorToolboxPanelAction extends SelectableAction {
public static final String id = "mapEditorToolboxPanelAction";
/**
* Creates this action.
*
* @param freeColClient The {@code FreeColClient} for the game.
*/
public MapEditorToolboxPanelAction(FreeColClient freeColClient) {
super(freeColClient, id);
setSelected(isSelected());
}
@Override
protected boolean shouldBeEnabled() {
return freeColClient.isMapEditor() && getGame() != null && getGame().getMap() != null;
}
@Override
protected boolean shouldBeSelected() {
return getGUI().isShowingMapEditorToolboxPanel();
}
// Interface ActionListener
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent ae) {
getGUI().enableEditorToolboxPanel(isEnabled() && isSelected());
}
}

View File

@ -28,7 +28,7 @@ import net.sf.freecol.client.FreeColClient;
/**
* An action for displaying the map controls.
* An action for displaying the map editor tile transform panel.
*/
public class MapEditorTransformPanelAction extends SelectableAction {

View File

@ -64,6 +64,7 @@ import net.sf.freecol.client.gui.SwingGUI;
import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Ability;
import net.sf.freecol.common.model.Area;
import net.sf.freecol.common.model.BuildableType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Direction;
@ -390,9 +391,10 @@ public final class MapViewer extends FreeColClientHolder {
final long animatedBaseMs = now();
if (!dirtyClipBounds.isEmpty()) {
displayToNonAnimationBufferImage(mapViewerBounds, dirtyClipBounds, nonAnimationG2d, map, useBuffers);
if (useBuffers) {
nonAnimationG2d.dispose();
}
}
if (useBuffers) {
nonAnimationG2d.dispose();
}
final long nonAnimatedMs = now();
@ -702,6 +704,10 @@ public final class MapViewer extends FreeColClientHolder {
});
displayDebugAiDefensiveMap(nonAnimationG2d, tcb);
if (getFreeColClient().isMapEditor()) {
displayAreasInMapEditor(nonAnimationG2d, tcb);
}
// Display the colony names, if needed
long t14 = now();
@ -753,6 +759,32 @@ public final class MapViewer extends FreeColClientHolder {
}
}
private void displayAreasInMapEditor(Graphics2D nonAnimationG2d, TileClippingBounds tcb) {
final Object oldAntialiasingHint = nonAnimationG2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
nonAnimationG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
final Composite oldComposite = nonAnimationG2d.getComposite();
nonAnimationG2d.setColor(Color.BLACK);
nonAnimationG2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
final Color oldColor = nonAnimationG2d.getColor();
paintEachTile(nonAnimationG2d, tcb, (tileG2d, tile) -> {
// This can easily be optimized if slow on some systems.
final List<Area> areas = getGame().getAreas();
// TODO: Support showing multiple areas by drawing in a checkered/blinds pattern.
final Area area = areas.stream().filter(a -> a.containsTile(tile)).findAny().orElse(null);
if (area != null) {
tileG2d.setColor(area.getColor());
tileG2d.fill(mapViewerScaledUtils.getFog());
}
});
nonAnimationG2d.setColor(oldColor);
nonAnimationG2d.setComposite(oldComposite);
nonAnimationG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntialiasingHint);
}
private void displayDebugAiDefensiveMap(Graphics2D nonAnimationG2d, TileClippingBounds tcb) {
if (FreeColDebugger.debugShowDefenceMapForPlayer() != null
&& getFreeColServer() != null

View File

@ -33,6 +33,7 @@ import net.sf.freecol.client.gui.action.DisplayGridAction;
import net.sf.freecol.client.gui.action.DisplayTileTextAction;
import net.sf.freecol.client.gui.action.DisplayTileTextAction.DisplayText;
import net.sf.freecol.client.gui.action.MapControlsAction;
import net.sf.freecol.client.gui.action.MapEditorToolboxPanelAction;
import net.sf.freecol.client.gui.action.MapEditorTransformPanelAction;
import net.sf.freecol.client.gui.action.NewAction;
import net.sf.freecol.client.gui.action.NewEmptyMapAction;
@ -131,6 +132,7 @@ public class MapEditorMenuBar extends FreeColMenuBar {
menu.setMnemonic(KeyEvent.VK_V);
menu.add(getCheckBoxMenuItem(MapControlsAction.id));
menu.add(getCheckBoxMenuItem(MapEditorToolboxPanelAction.id));
menu.add(getCheckBoxMenuItem(MapEditorTransformPanelAction.id));
menu.add(getCheckBoxMenuItem(DisplayGridAction.id));
menu.add(getCheckBoxMenuItem(ChangeWindowedModeAction.id));

View File

@ -0,0 +1,138 @@
/**
* Copyright (C) 2002-2022 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol 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.
*
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.gui.panel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.MapEditorController;
import net.sf.freecol.client.control.MapEditorTool;
import net.sf.freecol.client.gui.ImageLibrary;
import net.sf.freecol.client.gui.SwingGUI.PopupPosition;
import net.sf.freecol.client.gui.panel.WrapLayout.HorizontalAlignment;
import net.sf.freecol.client.gui.panel.WrapLayout.HorizontalGap;
import net.sf.freecol.common.i18n.Messages;
/**
* A panel for choosing the current tool in the map editor.
*
* This panel is only used when running in
* {@link net.sf.freecol.client.FreeColClient#isMapEditor() map editor mode}.
*
* @see MapEditorTransformPanel
*/
public final class MapEditorToolboxPanel extends FreeColPanel {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(MapEditorToolboxPanel.class.getName());
private final JPanel listPanel;
private final ButtonGroup group;
/**
* Creates a panel for choosing the current tool in the map editor.
*
* @param freeColClient The {@code FreeColClient} for the game.
*/
public MapEditorToolboxPanel(FreeColClient freeColClient) {
super(freeColClient, null, new BorderLayout());
final Dimension terrainSize = ImageLibrary.scaleDimension(getImageLibrary().scale(ImageLibrary.TILE_OVERLAY_SIZE), ImageLibrary.SMALLER_SCALE);
listPanel = new JPanel(new WrapLayout()
.withForceComponentSize(terrainSize)
.withHorizontalAlignment(HorizontalAlignment.LEFT)
.withHorizontalGap(HorizontalGap.AUTO));
group = new ButtonGroup();
//Add an invisible, move button to de-select all others
group.add(new JToggleButton());
buildList();
JScrollPane sl = new JScrollPane(listPanel,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
sl.getViewport().setOpaque(false);
listPanel.setSize(new Dimension(terrainSize.width * 3, 0));
add(sl, BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder());
revalidate();
repaint();
}
@Override
public String getFrameTitle() {
return Messages.message("mapEditor.toolBoxPanel.title");
}
@Override
public PopupPosition getFramePopupPosition() {
return PopupPosition.UPPER_RIGHT;
}
/**
* Builds the buttons for all the tools.
*/
private void buildList() {
final MapEditorController ctlr = getFreeColClient().getMapEditorController();
final MapEditorTool defaultTool = ctlr.getCurrentTool();
for (MapEditorTool mapEditorTool : MapEditorTool.values()) {
final boolean isDefaultTool = (mapEditorTool == defaultTool);
listPanel.add(buildButton(mapEditorTool, isDefaultTool));
}
}
private JToggleButton buildButton(MapEditorTool mapEditorTool, boolean defaultTool) {
final String text = Messages.getName(mapEditorTool.getId());
final MapEditorController ctlr = getFreeColClient().getMapEditorController();
/* TODO: Update description panel?
JPanel descriptionPanel = new JPanel(new BorderLayout());
descriptionPanel.add(new JLabel(new ImageIcon(image)),
BorderLayout.CENTER);
descriptionPanel.add(new JLabel(text, JLabel.CENTER),
BorderLayout.PAGE_END);
descriptionPanel.setBackground(Color.RED);
mt.setDescriptionPanel(descriptionPanel);
*/
final Dimension riverSize = ImageLibrary.scaleDimension(getImageLibrary().scale(ImageLibrary.TILE_SIZE), ImageLibrary.SMALLER_SCALE);
ImageIcon icon = new ImageIcon(getImageLibrary().getScaledImage("image.ui.includesSpecification")); // TODO: Add icon
final JToggleButton button = new JToggleButton(icon);
button.setToolTipText(text);
button.setOpaque(false);
button.setSelected(defaultTool);
group.add(button);
button.addActionListener((ActionEvent ae) -> {
});
button.setBorder(null);
return button;
}
}

View File

@ -45,11 +45,14 @@ import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.MapEditorController;
import net.sf.freecol.client.control.MapTransform;
import net.sf.freecol.client.gui.ChoiceItem;
import net.sf.freecol.client.gui.DialogHandler;
import net.sf.freecol.client.gui.ImageLibrary;
import net.sf.freecol.client.gui.SwingGUI.PopupPosition;
import net.sf.freecol.client.gui.panel.WrapLayout.HorizontalAlignment;
import net.sf.freecol.client.gui.panel.WrapLayout.HorizontalGap;
import net.sf.freecol.client.gui.panel.mapeditor.ChooseAreaModificationPanel;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Area;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.IndianNationType;
import net.sf.freecol.common.model.LostCityRumour;
@ -258,6 +261,37 @@ public final class MapEditorTransformPanel extends FreeColPanel {
t.removeLostCityRumour();
}
}
public static final class AssignAreaTransform extends MapTransform {
private Area area;
private AssignAreaTransform(Area area) {
this.area = area;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
/**
* {@inheritDoc}
*/
@Override
public void transform(Tile t) {
if (area == null) {
return;
}
if (area.containsTile(t)) {
area.removeTile(t);
} else {
area.addTile(t);
}
}
}
/** A native nation to use for native settlement type and skill. */
private static Nation nativeNation;
@ -265,6 +299,8 @@ public final class MapEditorTransformPanel extends FreeColPanel {
private final JPanel listPanel;
private JToggleButton settlementButton;
private final ButtonGroup group;
private final ChooseAreaModificationPanel chooseAreaModificationPanel;
/**
@ -275,6 +311,17 @@ public final class MapEditorTransformPanel extends FreeColPanel {
public MapEditorTransformPanel(FreeColClient freeColClient) {
super(freeColClient, null, new BorderLayout());
final MapEditorController ctlr = getFreeColClient().getMapEditorController();
final DialogHandler<Area> areaModificationHandler = (area) -> {
ctlr.setCurrentArea(area);
final MapTransform currentMapTransform = ctlr.getMapTransform();
if (currentMapTransform instanceof AssignAreaTransform) {
final AssignAreaTransform assignAreaTransform = (AssignAreaTransform) currentMapTransform;
assignAreaTransform.setArea(area);
}
};
this.chooseAreaModificationPanel = new ChooseAreaModificationPanel(freeColClient, areaModificationHandler );
final Dimension terrainSize = ImageLibrary.scaleDimension(getImageLibrary().scale(ImageLibrary.TILE_OVERLAY_SIZE), ImageLibrary.SMALLER_SCALE);
listPanel = new JPanel(new WrapLayout()
.withForceComponentSize(terrainSize)
@ -304,7 +351,7 @@ public final class MapEditorTransformPanel extends FreeColPanel {
@Override
public PopupPosition getFramePopupPosition() {
return PopupPosition.UPPER_RIGHT;
return PopupPosition.RIGHT;
}
/**
@ -375,6 +422,10 @@ public final class MapEditorTransformPanel extends FreeColPanel {
listPanel.add(buildButton(getImageLibrary().getSettlementTypeImage(settlementType, riverSize),
Messages.message("settlement"),
new SettlementTransform()));
listPanel.add(buildButton(getImageLibrary().getScaledImage("image.icon.mapEditor.selectArea"),
Messages.message("mapEditorTransformPanel.selectArea"),
new AssignAreaTransform(null)));
}
/**
@ -413,6 +464,10 @@ public final class MapEditorTransformPanel extends FreeColPanel {
.getRiverStyleKeys(all));
if (style != null) rst.setStyle(style);
}
if (mt instanceof AssignAreaTransform) {
getGUI().showFreeColPanel(chooseAreaModificationPanel, true, null, true);
newMapTransform = null;
}
newMapTransform = mt;
}
ctlr.setMapTransform(newMapTransform);

View File

@ -0,0 +1,91 @@
/**
* Copyright (C) 2002-2024 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol 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.
*
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.gui.panel.mapeditor;
import java.awt.Dimension;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import net.miginfocom.swing.MigLayout;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.gui.DialogHandler;
import net.sf.freecol.client.gui.panel.FreeColPanel;
import net.sf.freecol.client.gui.panel.WrapLayout;
import net.sf.freecol.client.gui.panel.WrapLayout.HorizontalAlignment;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Area;
/**
* A panel for choosing an area that tiles should be toggled in or out from.
*/
public final class ChooseAreaModificationPanel extends FreeColPanel {
private static final Logger logger = Logger.getLogger(ChooseAreaModificationPanel.class.getName());
private final DialogHandler<Area> dialogHandler;
/**
* Creates a new game panel.
*
* @param freeColClient The {@code FreeColClient} for the game.
*/
public ChooseAreaModificationPanel(FreeColClient freeColClient, DialogHandler<Area> dialogHandler) {
super(freeColClient, null, new MigLayout("fill, wrap 1", "[fill, growprio 150]", "[fill, growprio 150]"));
this.dialogHandler = dialogHandler;
final JPanel areasPanel = new JPanel(new WrapLayout()
.withHorizontalAlignment(HorizontalAlignment.CENTER)
) {
@Override
public Dimension getMinimumSize() {
return new Dimension(1, 1);
}
};
areasPanel.setOpaque(false);
final ButtonGroup bg = new ButtonGroup();
for (Area a : freeColClient.getGame().getAreas()) {
final String title = (a.getNameKey() != null) ? Messages.message(a.getNameKey()) : a.getName();
final JToggleButton areaButton = new JToggleButton(title);
areaButton.addActionListener((e) -> {
dialogHandler.handle(a);
});
bg.add(areaButton);
areasPanel.add(areaButton);
}
add(areasPanel, "grow, shrink");
setBorder(BorderFactory.createEmptyBorder());
setSize(getPreferredSize());
}
@Override
public String getFrameTitle() {
return Messages.message("mapEditor.chooseAreaModificationPanel.title");
}
}

View File

@ -19,11 +19,30 @@
package net.sf.freecol.server.generator;
import static net.sf.freecol.common.model.Constants.INFINITY;
import static net.sf.freecol.common.util.CollectionUtils.alwaysTrue;
import static net.sf.freecol.common.util.CollectionUtils.any;
import static net.sf.freecol.common.util.CollectionUtils.descendingListLengthComparator;
import static net.sf.freecol.common.util.CollectionUtils.find;
import static net.sf.freecol.common.util.CollectionUtils.forEachMapEntry;
import static net.sf.freecol.common.util.CollectionUtils.isNotNull;
import static net.sf.freecol.common.util.CollectionUtils.isNull;
import static net.sf.freecol.common.util.CollectionUtils.map;
import static net.sf.freecol.common.util.CollectionUtils.matchKeyEquals;
import static net.sf.freecol.common.util.CollectionUtils.minimize;
import static net.sf.freecol.common.util.CollectionUtils.toListNoNulls;
import static net.sf.freecol.common.util.CollectionUtils.transform;
import static net.sf.freecol.common.util.RandomUtils.getRandomMember;
import static net.sf.freecol.common.util.RandomUtils.randomInt;
import static net.sf.freecol.common.util.RandomUtils.randomShuffle;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
@ -37,10 +56,11 @@ import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Ability;
import net.sf.freecol.common.model.AbstractUnit;
import net.sf.freecol.common.model.Area;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.BuildingType;
import net.sf.freecol.common.model.Colony;
import static net.sf.freecol.common.model.Constants.*;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.EuropeanNationType;
import net.sf.freecol.common.model.Game;
@ -51,8 +71,6 @@ import net.sf.freecol.common.model.IndianSettlement;
import net.sf.freecol.common.model.LandMap;
import net.sf.freecol.common.model.LostCityRumour;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.Map.Position;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.NationType;
import net.sf.freecol.common.model.Player;
@ -69,8 +87,7 @@ import net.sf.freecol.common.option.MapGeneratorOptions;
import net.sf.freecol.common.option.OptionGroup;
import net.sf.freecol.common.util.LogBuilder;
import net.sf.freecol.common.util.RandomChoice;
import static net.sf.freecol.common.util.CollectionUtils.*;
import static net.sf.freecol.common.util.RandomUtils.*;
import net.sf.freecol.common.util.RandomUtils.RandomIntCache;
import net.sf.freecol.server.model.ServerBuilding;
import net.sf.freecol.server.model.ServerColony;
import net.sf.freecol.server.model.ServerIndianSettlement;
@ -96,41 +113,6 @@ public class SimpleMapGenerator implements MapGenerator {
*/
private static final float MIN_DISTANCE_FROM_POLE = 0.30f;
private static class Territory {
public ServerRegion region;
public Tile tile;
public final Player player;
public int numberOfSettlements;
public Territory(Player player, Tile tile) {
this.player = player;
this.tile = tile;
}
public Territory(Player player, ServerRegion region) {
this.player = player;
this.region = region;
}
public Tile getCenterTile(Map map) {
if (tile != null) return tile;
int[] xy = region.getCenter();
return map.getTile(xy[0], xy[1]);
}
// Override Object
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return player + " territory at " + region;
}
}
/** The random number source. */
private final Random random;
@ -317,164 +299,117 @@ public class SimpleMapGenerator implements MapGenerator {
LogBuilder lb) {
final Game game = map.getGame();
final Specification spec = game.getSpecification();
final boolean importSettlements = game.getMapGeneratorOptions()
.getBoolean(MapGeneratorOptions.IMPORT_SETTLEMENTS);
if (importSettlements && importMap != null) {
if (importIndianSettlements(map, importMap, lb)) return;
// Fall through and create them
final boolean importSettlements = game.getMapGeneratorOptions() .getBoolean(MapGeneratorOptions.IMPORT_SETTLEMENTS);
if (importSettlements && importMap != null && importIndianSettlements(map, importMap, lb)) {
return;
}
float shares = 0f;
List<IndianSettlement> settlements = new ArrayList<>();
HashMap<String, Territory> territoryMap = new HashMap<>();
List<Player> players = game.getLiveNativePlayerList();
List<Player> indians = new ArrayList<>(players.size());
for (Player player : players) {
switch (player.getNationType().getNumberOfSettlements()) {
case HIGH:
shares += 4;
break;
case AVERAGE:
shares += 3;
break;
case LOW:
shares += 2;
break;
}
indians.add(player);
List<String> regionKeys
= ((IndianNationType)player.getNationType()).getRegions();
Territory territory = null;
if (regionKeys == null || regionKeys.isEmpty()) {
territory = new Territory(player, map.getRandomLandTile(random));
territoryMap.put(player.getId(), territory);
} else {
for (String key : regionKeys) {
if (territoryMap.get(key) == null) {
ServerRegion region = (ServerRegion)map.getRegionByKey(key);
if (region == null) {
territory = new Territory(player, map.getRandomLandTile(random));
} else {
territory = new Territory(player, region);
}
territoryMap.put(key, territory);
lb.add("Allocated region ", key,
" for ", player, ".\n");
break;
}
}
if (territory == null) {
lb.add("Failed to allocate preferred region ",
first(regionKeys), " for ", player.getNation(), "\n");
outer: for (String key : regionKeys) {
Territory otherTerritory = territoryMap.get(key);
for (String otherKey : ((IndianNationType) otherTerritory.player.getNationType())
.getRegions()) {
if (territoryMap.get(otherKey) == null) {
ServerRegion foundRegion = otherTerritory.region;
otherTerritory.region = (ServerRegion)map.getRegionByKey(otherKey);
territoryMap.put(otherKey, otherTerritory);
territory = new Territory(player, foundRegion);
territoryMap.put(key, territory);
break outer;
}
}
}
if (territory == null) {
lb.add("Unable to find free region for ",
player.getName(), "\n");
territory = new Territory(player, map.getRandomLandTile(random));
territoryMap.put(player.getId(), territory);
}
}
}
final List<Player> nativePlayers = game.getLiveNativePlayerList();
if (nativePlayers.isEmpty()) {
return;
}
if (indians.isEmpty()) return;
// Examine all the non-polar settleable tiles in a random
// order picking out as many as possible suitable tiles for
// native settlements such that can be guaranteed at least one
// layer of surrounding tiles to own.
List<Tile> allTiles = map.getShuffledTiles(random);
final List<Tile> allTiles = map.getShuffledTiles(random);
final int minDistance = spec.getRange(GameOptions.SETTLEMENT_NUMBER);
List<Tile> settlementTiles = new ArrayList<>();
final Set<Tile> settlementTiles = new LinkedHashSet<>();
for (Tile tile : allTiles) {
if (!tile.isPolar()
&& suitableForNativeSettlement(tile)
&& none(settlementTiles, t -> t.getDistanceTo(tile) < minDistance))
settlementTiles.add(tile);
}
randomShuffle(logger, "Settlement tiles", settlementTiles, random);
// Check number of settlements.
int settlementsToPlace = settlementTiles.size();
float share = settlementsToPlace / shares;
if (settlementTiles.size() < indians.size()) {
// FIXME: something drastic to boost the settlement number
lb.add("There are only ", settlementTiles.size(),
" settlement sites.\n",
" This is smaller than ", indians.size(),
" the number of tribes.\n");
}
// Find the capitals
List<Territory> territories
= new ArrayList<>(territoryMap.values());
int settlementsPlaced = 0;
for (Territory territory : territories) {
switch (territory.player.getNationType().getNumberOfSettlements()) {
case HIGH:
territory.numberOfSettlements = Math.round(4 * share);
break;
case AVERAGE:
territory.numberOfSettlements = Math.round(3 * share);
break;
case LOW:
territory.numberOfSettlements = Math.round(2 * share);
break;
if (tile.isPolar()) {
continue;
}
int radius = territory.player.getNationType().getCapitalType()
.getClaimableRadius();
IndianSettlement is = placeCapital(map, territory, radius,
new ArrayList<>(settlementTiles), lb);
if (is != null) {
settlements.add(is);
settlementsPlaced++;
settlementTiles.remove(is.getTile());
if (!suitableForNativeSettlement(tile)) {
continue;
}
if (any(settlementTiles, t -> t.getDistanceTo(tile) < minDistance)) {
continue;
}
settlementTiles.add(tile);
}
// Sort tiles from the edges of the map inward
settlementTiles.sort(Tile.edgeDistanceComparator);
// Now place other settlements
while (!settlementTiles.isEmpty() && !territories.isEmpty()) {
Tile tile = settlementTiles.remove(0);
if (tile.getOwner() != null) continue; // No close overlap
Territory territory = getClosestTerritory(tile, territories);
int radius = territory.player.getNationType().getSettlementType(false)
.getClaimableRadius();
// Insist that the settlement can not be linear
if (territory.player.getClaimableTiles(tile, radius).size()
> 2 * radius + 1) {
String name = (territory.region == null) ? "default region"
: territory.region.toString();
lb.add("Placing a ", territory.player,
" camp in region: ", name,
" at tile: ", tile, "\n");
settlements.add(placeIndianSettlement(territory.player,
false, tile, map, lb));
settlementsPlaced++;
territory.numberOfSettlements--;
if (territory.numberOfSettlements <= 0) {
territories.remove(territory);
final java.util.Map<Player, Set<Tile>> designatedArea = new HashMap<>();
for (Player player : nativePlayers) {
final Area area = map.getGame().getNationStartingArea(player.getNation());
if (area != null && !area.isEmpty()) {
final Set<Tile> settlementTilesForNation = area.getTiles().stream()
.filter(t -> settlementTiles.contains(t))
.collect(Collectors.toCollection(LinkedHashSet::new));
if (settlementTilesForNation.isEmpty()) {
logger.warning("No settlements for nationType=" + player.getNationId());
}
designatedArea.put(player, settlementTilesForNation);
}
}
for (Player player : nativePlayers) {
if (designatedArea.containsKey(player)) {
continue;
}
final IndianNationType indianNationType = (IndianNationType) player.getNationType();
final List<Rectangle> otherRegions = indianNationType.getRegions().stream()
.map(key -> ((ServerRegion) map.getRegionByKey(key)).getBounds())
.filter(bounds -> !bounds.isEmpty())
.collect(Collectors.toList());
if (otherRegions.isEmpty()) {
logger.warning("No area or regions found for nationType=" + player.getNationId());
continue;
}
final Set<Tile> settlementTilesForNation = settlementTiles.stream()
.filter(t -> otherRegions.stream().anyMatch(bounds -> bounds.contains(t.getX(), t.getY())))
.collect(Collectors.toCollection(LinkedHashSet::new));
if (settlementTilesForNation.isEmpty()) {
logger.warning("No settlements in region for nationType=" + player.getNationId());
}
designatedArea.put(player, settlementTilesForNation);
}
// Place the capitals:
for (Player player : nativePlayers) {
final Set<Tile> allowedPlacements = designatedArea.get(player);
if (allowedPlacements == null) {
continue;
}
for (Tile settlementTile : new ArrayList<>(settlementTiles)) {
if (!allowedPlacements.contains(settlementTile)) {
continue;
}
final IndianSettlement capital = placeIndianSettlement(player, true, settlementTile, map, lb);
settlements.add(capital);
settlementTiles.remove(settlementTile);
break;
}
}
for (Tile settlementTile : settlementTiles) {
final List<Player> players = new ArrayList<>(designatedArea.keySet());
Collections.shuffle(players, random);
final Player owner = players.stream().filter(p -> {
final Set<Tile> tiles = designatedArea.get(p);
if (tiles == null) {
return false;
}
return tiles.contains(settlementTile);
})
.findFirst()
.orElse(null);
if (owner == null) {
continue;
}
final IndianSettlement settlement = placeIndianSettlement(owner, false, settlementTile, map, lb);
settlements.add(settlement);
}
// Grow some more tiles.
// FIXME: move the magic numbers below to the spec
// Also collect the skills provided
@ -566,8 +501,6 @@ public class SimpleMapGenerator implements MapGenerator {
" x ", iss.get(0).getLearnableSkill().getSuffix());
}
}
lb.add("\nCreated ", settlementsPlaced,
" Indian settlements of maximum ", settlementsToPlace, ".\n");
}
/**
@ -614,58 +547,6 @@ public class SimpleMapGenerator implements MapGenerator {
return null;
}
/**
* Find the closest territory to a given tile from a list of choices.
*
* @param tile The {@code Tile} to search from.
* @param territories The list of {@code Territory}s to choose from.
* @return The closest {@code Territory} found, or null if none.
*/
private Territory getClosestTerritory(final Tile tile,
List<Territory> territories) {
final Map map = tile.getMap();
final Comparator<Territory> comp = Comparator.comparingInt(t ->
map.getDistance(tile, t.getCenterTile(map)));
return minimize(territories, comp);
}
/**
* Place a native capital in a territory.
*
* @param map The {@code Map} to place the settlement in.
* @param territory The {@code Territory} within the map.
* @param radius The settlement radius.
* @param tiles A list of {@code Tile}s to select from.
* @param lb A {@code LogBuilder} to log to.
* @return The {@code IndianSettlement} placed, or null if
* none placed.
*/
private IndianSettlement placeCapital(final Map map, Territory territory,
int radius, List<Tile> tiles,
LogBuilder lb) {
final Tile center = territory.getCenterTile(map);
final Predicate<Tile> terrPred = t ->
territory.player.getClaimableTiles(t, radius).size()
>= (2 * radius + 1) * (2 * radius + 1) / 2;
final Comparator<Tile> comp
= Comparator.comparingInt(t -> t.getDistanceTo(center));
// Choose a tile that is free and half the expected tile claims
// can succeed, preventing capitals on small islands.
Tile t = first(transform(tiles, terrPred, Function.<Tile>identity(),
comp));
if (t == null) return null;
String name = (territory.region == null) ? "default region"
: territory.region.toString();
lb.add("Placing the ", territory.player,
" capital in region: ", name, " at tile: ", t, "\n");
IndianSettlement is = placeIndianSettlement(territory.player,
true, t, map, lb);
territory.numberOfSettlements--;
territory.tile = t;
return is;
}
/**
* Builds a {@code IndianSettlement} at the given position.
*
@ -1132,7 +1013,7 @@ public class SimpleMapGenerator implements MapGenerator {
// Create terrain.
Map map = new TerrainGenerator(this.random)
.generateMap(game, importMap, landMap, lb);
// Decorate the map.
makeNativeSettlements(map, importMap, lb);
makeLostCityRumours(map, importMap, lb);

View File

@ -30,6 +30,7 @@ import java.util.logging.Logger;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.LandMap;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Area;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.Region;
import net.sf.freecol.common.model.Region.RegionType;
@ -931,6 +932,13 @@ public class TerrainGenerator {
// Probably only needed on import of old maps.
map.fixupRegions();
if (importMap != null) {
for (Area importMapArea : importMap.getGame().getAreas()) {
game.addArea(new Area(game, importMapArea));
}
}
game.generateDefaultAreas();
// Add the bonuses only after the map is completed.
// Otherwise we risk creating resources on fields where they