2020-09-11 23:46:04 +03:00
//
// Copyright 2020 Electronic Arts Inc.
//
2023-06-09 11:44:39 +02:00
// The Command & Conquer Map Editor and corresponding source code 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,
2020-09-11 23:46:04 +03:00
// either version 3 of the License, or (at your option) any later version.
2024-01-31 13:25:32 +01:00
//
2023-06-09 11:44:39 +02:00
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
2020-09-11 23:46:04 +03:00
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
2023-07-21 13:15:41 +02:00
2020-09-11 23:46:04 +03:00
using MobiusEditor.Interface ;
using MobiusEditor.Model ;
using MobiusEditor.Utility ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Drawing ;
2022-08-11 22:49:22 +02:00
using System.Drawing.Drawing2D ;
2020-09-11 23:46:04 +03:00
using System.Drawing.Imaging ;
2023-07-05 21:49:17 +02:00
using System.Drawing.Text ;
2020-09-11 23:46:04 +03:00
using System.Linq ;
2024-03-28 19:22:06 +01:00
using System.Text ;
2020-09-11 23:46:04 +03:00
namespace MobiusEditor.Render
{
2023-07-20 01:07:00 +02:00
public class RenderInfo
{
// paint position, paint action, true if flat, sub-position.
// This works in 'classic pixels'; 1/24th of a cell.
public Point RenderBasePoint ;
public Action < Graphics > RenderAction { get ; private set ; }
2023-08-03 18:56:02 +02:00
public int ZOrder { get ; private set ; }
2023-07-20 01:07:00 +02:00
public ITechno RenderedObject { get ; private set ; }
public bool IsRendered { get ; set ; }
2023-08-03 18:56:02 +02:00
public RenderInfo ( Point renderPosition , Action < Graphics > paintAction , int zOrder , ITechno paintedObject )
2023-07-20 01:07:00 +02:00
{
this . RenderBasePoint = renderPosition ;
this . RenderAction = paintAction ;
2023-08-03 18:56:02 +02:00
this . ZOrder = zOrder ;
2023-07-20 01:07:00 +02:00
this . RenderedObject = paintedObject ;
this . IsRendered = false ;
}
2023-08-03 18:56:02 +02:00
public RenderInfo ( Point renderPosition , Action < Graphics > paintAction , ITechno paintedObject )
2024-07-29 18:38:08 +02:00
: this ( renderPosition , paintAction , Globals . ZOrderDefault , paintedObject )
2023-08-03 18:56:02 +02:00
{
}
2023-07-20 01:07:00 +02:00
}
2020-09-11 23:46:04 +03:00
public static class MapRenderer
{
private static readonly int [ ] Facing16 = new int [ 256 ]
{
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ,
2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 6 , 6 , 6 , 6 , 6 , 6 , 6 , 6 ,
6 , 6 , 6 , 6 , 6 , 6 , 6 , 6 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 8 , 8 , 8 , 8 , 8 , 8 , 8 , 8 ,
8 , 8 , 8 , 8 , 8 , 8 , 8 , 8 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 ,
10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 ,
12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 ,
14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
} ;
private static readonly int [ ] Facing32 = new int [ 256 ]
{
0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 ,
3 , 4 , 4 , 4 , 4 , 4 , 4 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 6 , 6 , 6 , 6 , 6 , 6 , 6 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 8 , 8 , 8 , 8 ,
8 , 8 , 8 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 11 , 11 , 11 , 11 , 11 , 11 , 11 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 ,
13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 15 , 16 , 16 , 16 , 16 , 16 , 16 ,
16 , 16 , 16 , 16 , 16 , 17 , 17 , 17 , 17 , 17 , 17 , 17 , 17 , 17 , 18 , 18 , 18 , 18 , 18 , 18 , 18 , 18 , 18 , 19 , 19 , 19 , 19 , 19 , 19 , 19 , 19 , 19 ,
19 , 20 , 20 , 20 , 20 , 20 , 20 , 21 , 21 , 21 , 21 , 21 , 21 , 21 , 22 , 22 , 22 , 22 , 22 , 22 , 22 , 23 , 23 , 23 , 23 , 23 , 23 , 23 , 24 , 24 , 24 , 24 ,
24 , 24 , 24 , 25 , 25 , 25 , 25 , 25 , 25 , 25 , 26 , 26 , 26 , 26 , 26 , 26 , 26 , 27 , 27 , 27 , 27 , 27 , 27 , 27 , 28 , 28 , 28 , 28 , 28 , 28 , 28 , 28 ,
29 , 29 , 29 , 29 , 29 , 29 , 29 , 29 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 0 , 0 , 0 , 0 , 0 , 0
} ;
private static readonly int [ ] HumanShape = new int [ 32 ]
{
0 , 0 , 7 , 7 , 7 , 7 , 6 , 6 , 6 , 6 , 5 , 5 , 5 , 5 , 5 , 4 , 4 , 4 , 3 , 3 , 3 , 3 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 , 0
} ;
private static readonly int [ ] BodyShape = new int [ 32 ]
{
0 , 31 , 30 , 29 , 28 , 27 , 26 , 25 , 24 , 23 , 22 , 21 , 20 , 19 , 18 , 17 , 16 , 15 , 14 , 13 , 12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1
} ;
2022-11-27 20:48:33 +01:00
/// <summary>
2024-05-21 18:51:35 +02:00
/// Cosine table. Technically signed bytes, but stored as 00-FF for simplicity.
2022-11-27 20:48:33 +01:00
/// </summary>
private static byte [ ] CosTable = {
0x00 , 0x03 , 0x06 , 0x09 , 0x0c , 0x0f , 0x12 , 0x15 , 0x18 , 0x1b , 0x1e , 0x21 , 0x24 , 0x27 , 0x2a , 0x2d ,
0x30 , 0x33 , 0x36 , 0x39 , 0x3b , 0x3e , 0x41 , 0x43 , 0x46 , 0x49 , 0x4b , 0x4e , 0x50 , 0x52 , 0x55 , 0x57 ,
0x59 , 0x5b , 0x5e , 0x60 , 0x62 , 0x64 , 0x65 , 0x67 , 0x69 , 0x6b , 0x6c , 0x6e , 0x6f , 0x71 , 0x72 , 0x74 ,
0x75 , 0x76 , 0x77 , 0x78 , 0x79 , 0x7a , 0x7b , 0x7b , 0x7c , 0x7d , 0x7d , 0x7e , 0x7e , 0x7e , 0x7e , 0x7e ,
0x7f , 0x7e , 0x7e , 0x7e , 0x7e , 0x7e , 0x7d , 0x7d , 0x7c , 0x7b , 0x7b , 0x7a , 0x79 , 0x78 , 0x77 , 0x76 ,
0x75 , 0x74 , 0x72 , 0x71 , 0x70 , 0x6e , 0x6c , 0x6b , 0x69 , 0x67 , 0x66 , 0x64 , 0x62 , 0x60 , 0x5e , 0x5b ,
0x59 , 0x57 , 0x55 , 0x52 , 0x50 , 0x4e , 0x4b , 0x49 , 0x46 , 0x43 , 0x41 , 0x3e , 0x3b , 0x39 , 0x36 , 0x33 ,
0x30 , 0x2d , 0x2a , 0x27 , 0x24 , 0x21 , 0x1e , 0x1b , 0x18 , 0x15 , 0x12 , 0x0f , 0x0c , 0x09 , 0x06 , 0x03 ,
0x00 , 0xfd , 0xfa , 0xf7 , 0xf4 , 0xf1 , 0xee , 0xeb , 0xe8 , 0xe5 , 0xe2 , 0xdf , 0xdc , 0xd9 , 0xd6 , 0xd3 ,
0xd0 , 0xcd , 0xca , 0xc7 , 0xc5 , 0xc2 , 0xbf , 0xbd , 0xba , 0xb7 , 0xb5 , 0xb2 , 0xb0 , 0xae , 0xab , 0xa9 ,
0xa7 , 0xa5 , 0xa2 , 0xa0 , 0x9e , 0x9c , 0x9a , 0x99 , 0x97 , 0x95 , 0x94 , 0x92 , 0x91 , 0x8f , 0x8e , 0x8c ,
0x8b , 0x8a , 0x89 , 0x88 , 0x87 , 0x86 , 0x85 , 0x85 , 0x84 , 0x83 , 0x83 , 0x82 , 0x82 , 0x82 , 0x82 , 0x82 ,
0x82 , 0x82 , 0x82 , 0x82 , 0x82 , 0x82 , 0x83 , 0x83 , 0x84 , 0x85 , 0x85 , 0x86 , 0x87 , 0x88 , 0x89 , 0x8a ,
0x8b , 0x8c , 0x8e , 0x8f , 0x90 , 0x92 , 0x94 , 0x95 , 0x97 , 0x99 , 0x9a , 0x9c , 0x9e , 0xa0 , 0xa2 , 0xa5 ,
0xa7 , 0xa9 , 0xab , 0xae , 0xb0 , 0xb2 , 0xb5 , 0xb7 , 0xba , 0xbd , 0xbf , 0xc2 , 0xc5 , 0xc7 , 0xca , 0xcd ,
0xd0 , 0xd3 , 0xd6 , 0xd9 , 0xdc , 0xdf , 0xe2 , 0xe5 , 0xe8 , 0xeb , 0xee , 0xf1 , 0xf4 , 0xf7 , 0xfa , 0xfd ,
} ;
/// <summary>
2024-05-21 18:51:35 +02:00
/// Sine table. Technically signed bytes, but stored as 00-FF for simplicity.
2022-11-27 20:48:33 +01:00
/// </summary>
private static byte [ ] SinTable = {
0x7f , 0x7e , 0x7e , 0x7e , 0x7e , 0x7e , 0x7d , 0x7d , 0x7c , 0x7b , 0x7b , 0x7a , 0x79 , 0x78 , 0x77 , 0x76 ,
0x75 , 0x74 , 0x72 , 0x71 , 0x70 , 0x6e , 0x6c , 0x6b , 0x69 , 0x67 , 0x66 , 0x64 , 0x62 , 0x60 , 0x5e , 0x5b ,
0x59 , 0x57 , 0x55 , 0x52 , 0x50 , 0x4e , 0x4b , 0x49 , 0x46 , 0x43 , 0x41 , 0x3e , 0x3b , 0x39 , 0x36 , 0x33 ,
0x30 , 0x2d , 0x2a , 0x27 , 0x24 , 0x21 , 0x1e , 0x1b , 0x18 , 0x15 , 0x12 , 0x0f , 0x0c , 0x09 , 0x06 , 0x03 ,
0x00 , 0xfd , 0xfa , 0xf7 , 0xf4 , 0xf1 , 0xee , 0xeb , 0xe8 , 0xe5 , 0xe2 , 0xdf , 0xdc , 0xd9 , 0xd6 , 0xd3 ,
0xd0 , 0xcd , 0xca , 0xc7 , 0xc5 , 0xc2 , 0xbf , 0xbd , 0xba , 0xb7 , 0xb5 , 0xb2 , 0xb0 , 0xae , 0xab , 0xa9 ,
0xa7 , 0xa5 , 0xa2 , 0xa0 , 0x9e , 0x9c , 0x9a , 0x99 , 0x97 , 0x95 , 0x94 , 0x92 , 0x91 , 0x8f , 0x8e , 0x8c ,
0x8b , 0x8a , 0x89 , 0x88 , 0x87 , 0x86 , 0x85 , 0x85 , 0x84 , 0x83 , 0x83 , 0x82 , 0x82 , 0x82 , 0x82 , 0x82 ,
0x82 , 0x82 , 0x82 , 0x82 , 0x82 , 0x82 , 0x83 , 0x83 , 0x84 , 0x85 , 0x85 , 0x86 , 0x87 , 0x88 , 0x89 , 0x8a ,
0x8b , 0x8c , 0x8e , 0x8f , 0x90 , 0x92 , 0x94 , 0x95 , 0x97 , 0x99 , 0x9a , 0x9c , 0x9e , 0xa0 , 0xa2 , 0xa5 ,
0xa7 , 0xa9 , 0xab , 0xae , 0xb0 , 0xb2 , 0xb5 , 0xb7 , 0xba , 0xbd , 0xbf , 0xc2 , 0xc5 , 0xc7 , 0xca , 0xcd ,
0xd0 , 0xd3 , 0xd6 , 0xd9 , 0xdc , 0xdf , 0xe2 , 0xe5 , 0xe8 , 0xeb , 0xee , 0xf1 , 0xf4 , 0xf7 , 0xfa , 0xfd ,
0x00 , 0x03 , 0x06 , 0x09 , 0x0c , 0x0f , 0x12 , 0x15 , 0x18 , 0x1b , 0x1e , 0x21 , 0x24 , 0x27 , 0x2a , 0x2d ,
0x30 , 0x33 , 0x36 , 0x39 , 0x3b , 0x3e , 0x41 , 0x43 , 0x46 , 0x49 , 0x4b , 0x4e , 0x50 , 0x52 , 0x55 , 0x57 ,
0x59 , 0x5b , 0x5e , 0x60 , 0x62 , 0x64 , 0x65 , 0x67 , 0x69 , 0x6b , 0x6c , 0x6e , 0x6f , 0x71 , 0x72 , 0x74 ,
0x75 , 0x76 , 0x77 , 0x78 , 0x79 , 0x7a , 0x7b , 0x7b , 0x7c , 0x7d , 0x7d , 0x7e , 0x7e , 0x7e , 0x7e , 0x7e ,
} ;
private static void MovePoint ( ref int x , ref int y , byte dir , int distance , int perspectiveDivider )
{
x + = ( ( sbyte ) CosTable [ dir ] * distance ) > > 7 ;
y + = - ( ( ( sbyte ) SinTable [ dir ] * distance / perspectiveDivider ) > > 7 ) ;
}
private static readonly short [ ] HeliDistanceAdjust = { 8 , 9 , 10 , 9 , 8 , 9 , 10 , 9 } ;
private static readonly Point [ ] BackTurretAdjust = new Point [ ]
2020-09-11 23:46:04 +03:00
{
2022-08-14 15:12:30 +02:00
new Point ( 1 , 2 ) , // N
2020-09-11 23:46:04 +03:00
new Point ( - 1 , 1 ) ,
new Point ( - 2 , 0 ) ,
new Point ( - 3 , 0 ) ,
2022-08-14 15:12:30 +02:00
new Point ( - 3 , 1 ) , // NW
2020-09-11 23:46:04 +03:00
new Point ( - 4 , - 1 ) ,
new Point ( - 4 , - 1 ) ,
new Point ( - 5 , - 2 ) ,
2022-08-14 15:12:30 +02:00
new Point ( - 5 , - 3 ) , // W
2020-09-11 23:46:04 +03:00
new Point ( - 5 , - 3 ) ,
new Point ( - 3 , - 3 ) ,
new Point ( - 3 , - 4 ) ,
2022-08-14 15:12:30 +02:00
new Point ( - 3 , - 4 ) , // SW
2020-09-11 23:46:04 +03:00
new Point ( - 3 , - 5 ) ,
new Point ( - 2 , - 5 ) ,
new Point ( - 1 , - 5 ) ,
2022-08-14 15:12:30 +02:00
new Point ( 0 , - 5 ) , // S
2020-09-11 23:46:04 +03:00
new Point ( 1 , - 6 ) ,
new Point ( 2 , - 5 ) ,
new Point ( 3 , - 5 ) ,
2022-08-14 15:12:30 +02:00
new Point ( 4 , - 5 ) , // SE
2020-09-11 23:46:04 +03:00
new Point ( 6 , - 4 ) ,
new Point ( 6 , - 3 ) ,
new Point ( 6 , - 3 ) ,
2022-08-14 15:12:30 +02:00
new Point ( 6 , - 3 ) , // E
2020-09-11 23:46:04 +03:00
new Point ( 5 , - 1 ) ,
new Point ( 5 , - 1 ) ,
new Point ( 4 , 0 ) ,
2022-08-14 15:12:30 +02:00
new Point ( 3 , 0 ) , // NE
2020-09-11 23:46:04 +03:00
new Point ( 2 , 0 ) ,
new Point ( 2 , 1 ) ,
new Point ( 1 , 2 )
} ;
2024-07-19 15:41:12 +02:00
public static void Render ( GameInfo gameInfo , Map map , Graphics graphics , ISet < Point > locations , MapLayerFlag layers , double tileScale , ShapeCacheManager cacheManager )
2020-09-11 23:46:04 +03:00
{
2024-07-19 15:41:12 +02:00
bool disposeCacheManager = false ;
if ( cacheManager = = null )
{
cacheManager = new ShapeCacheManager ( ) ;
disposeCacheManager = true ;
}
2023-06-21 12:18:49 +02:00
// tileScale should always be given so it results in an exact integer tile size. Math.Round was added to account for .999 situations in the floats.
Size tileSize = new Size ( Math . Max ( 1 , ( int ) Math . Round ( Globals . OriginalTileWidth * tileScale ) ) , Math . Max ( 1 , ( int ) Math . Round ( Globals . OriginalTileHeight * tileScale ) ) ) ;
//Size tileSize = new Size(Math.Max(1, (int)(Globals.OriginalTileWidth * tileScale)), Math.Max(1, (int)(Globals.OriginalTileHeight * tileScale)));
2023-06-19 15:28:04 +02:00
TheaterType theater = map . Theater ;
2023-07-20 01:07:00 +02:00
// paint position, paint action, true if flat, sub-position.
List < RenderInfo > overlappingRenderList = new List < RenderInfo > ( ) ;
2020-09-11 23:46:04 +03:00
Func < IEnumerable < Point > > renderLocations = null ;
if ( locations ! = null )
{
2023-07-09 00:55:19 +02:00
renderLocations = ( ) = > locations . OrderBy ( p = > p . Y * map . Metrics . Width + p . X ) ;
2020-09-11 23:46:04 +03:00
}
else
{
IEnumerable < Point > allCells ( )
{
2024-05-10 00:03:03 +02:00
for ( int y = 0 ; y < map . Metrics . Height ; + + y )
2020-09-11 23:46:04 +03:00
{
2024-05-10 00:03:03 +02:00
for ( int x = 0 ; x < map . Metrics . Width ; + + x )
2020-09-11 23:46:04 +03:00
{
yield return new Point ( x , y ) ;
}
}
}
renderLocations = allCells ;
}
2024-05-21 18:51:35 +02:00
CompositingQuality backupCompositingQuality = graphics . CompositingQuality ;
InterpolationMode backupInterpolationMode = graphics . InterpolationMode ;
SmoothingMode backupSmoothingMode = graphics . SmoothingMode ;
PixelOffsetMode backupPixelOffsetMode = graphics . PixelOffsetMode ;
2024-05-22 17:43:25 +02:00
// Check if high-quality tile resizing is useful.
SetRenderSettings ( graphics , false ) ;
bool isSmooth = backupCompositingQuality ! = graphics . CompositingQuality | |
backupInterpolationMode ! = graphics . InterpolationMode | |
backupSmoothingMode ! = graphics . SmoothingMode | |
backupPixelOffsetMode ! = graphics . PixelOffsetMode ;
// No need to restore the settings; the high quality tile resizing makes all tiles the
// required size, and if isSmooth is false, the settings were already on pixel resize.
2020-09-11 23:46:04 +03:00
if ( ( layers & MapLayerFlag . Template ) ! = MapLayerFlag . None )
{
2022-08-11 22:49:22 +02:00
TemplateType clear = map . TemplateTypes . Where ( t = > t . Flag = = TemplateTypeFlag . Clear ) . FirstOrDefault ( ) ;
2024-07-19 15:41:12 +02:00
foreach ( Point topLeft in renderLocations ( ) )
{
Template template = map . Templates [ topLeft ] ;
TemplateType ttype = template ? . Type ? ? clear ;
string name = ttype . Name ;
// For clear terrain, calculate icon from 0-15 using map position.
int icon = template ? . Icon ? ? ( ( topLeft . X & 0x03 ) | ( ( topLeft . Y ) & 0x03 ) < < 2 ) ;
// If something is actually placed on the map, show it, even if it has no graphics.
string tileName = "template_" + name + "_" + icon . ToString ( "D4" ) + "_" + tileSize . Width + "x" + tileSize . Height + ( isSmooth ? "_smooth" : String . Empty ) ;
Bitmap tileImg = cacheManager . GetImage ( tileName ) ;
Rectangle renderBounds = new Rectangle ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height , tileSize . Width , tileSize . Height ) ;
if ( tileImg = = null )
{
2024-05-21 18:51:35 +02:00
bool success = Globals . TheTilesetManager . GetTileData ( name , icon , out Tile tile , true , false ) ;
2024-07-19 15:41:12 +02:00
if ( tile ! = null & & tile . Image ! = null )
2022-08-18 00:41:47 +02:00
{
2024-07-19 15:41:12 +02:00
Bitmap tileImage = tile . Image ;
if ( ! isSmooth | | ( tileSize . Width = = tileImage . Width & & tileSize . Height = = tileImage . Height ) )
2024-05-21 18:51:35 +02:00
{
2024-07-19 15:41:12 +02:00
tileImg = tileImage . RemoveAlpha ( ) ;
2024-05-21 18:51:35 +02:00
}
2024-07-19 15:41:12 +02:00
else
2022-08-18 00:41:47 +02:00
{
2024-07-19 15:41:12 +02:00
// Results in a new image that is scaled to the correct size, without edge artifacts.
Bitmap scaledImage = tileImage . HighQualityScale ( tileSize . Width , tileSize . Height ,
backupCompositingQuality , backupInterpolationMode , backupSmoothingMode , backupPixelOffsetMode ) ;
scaledImage . RemoveAlphaOnCurrent ( ) ;
tileImg = scaledImage ;
2022-08-18 00:41:47 +02:00
}
2024-07-19 15:41:12 +02:00
cacheManager . AddImage ( tileName , tileImg ) ;
2024-05-21 18:51:35 +02:00
}
2020-09-11 23:46:04 +03:00
}
2024-07-19 15:41:12 +02:00
if ( tileImg ! = null )
2020-09-11 23:46:04 +03:00
{
2024-07-19 15:41:12 +02:00
graphics . DrawImage ( tileImg , renderBounds ) ;
}
else
{
Debug . Print ( string . Format ( "Template {0} ({1}) could not be rendered." , name , icon ) ) ;
2020-09-11 23:46:04 +03:00
}
}
}
2024-07-19 15:41:12 +02:00
// Since high-quality scaling is now done on the tiles themselves, the actual map tile painting is done
// with pixel interpolation mode because it is faster, and the tiles are already correctly sized anyway.
// So now, restore the actual requested settings.
2024-05-22 17:43:25 +02:00
graphics . CompositingQuality = backupCompositingQuality ;
graphics . InterpolationMode = backupInterpolationMode ;
graphics . SmoothingMode = backupSmoothingMode ;
graphics . PixelOffsetMode = backupPixelOffsetMode ;
2022-09-01 13:05:49 +02:00
// Attached bibs are counted under Buildings, not Smudge.
if ( ( layers & MapLayerFlag . Buildings ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( Point topLeft in renderLocations ( ) )
2022-09-01 13:05:49 +02:00
{
2023-06-18 16:56:33 +02:00
Smudge smudge = map . Smudge [ topLeft ] ;
2023-06-19 15:28:04 +02:00
// Don't render bibs in theaters which don't contain them.
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
if ( smudge ! = null & & smudge . Type . IsAutoBib & & ( ! Globals . FilterTheaterObjects | | smudge . Type . ExistsInTheater ) )
2022-09-01 13:05:49 +02:00
{
2024-07-19 15:41:12 +02:00
RenderSmudge ( topLeft , tileSize , tileScale , smudge , isSmooth , cacheManager ) . Item2 ( graphics ) ;
2022-09-01 13:05:49 +02:00
}
}
}
2020-09-11 23:46:04 +03:00
if ( ( layers & MapLayerFlag . Smudge ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( Point topLeft in renderLocations ( ) )
2020-09-11 23:46:04 +03:00
{
2023-06-18 16:56:33 +02:00
Smudge smudge = map . Smudge [ topLeft ] ;
2022-09-05 16:22:14 +02:00
if ( smudge ! = null & & ! smudge . Type . IsAutoBib )
2020-09-11 23:46:04 +03:00
{
2024-07-19 15:41:12 +02:00
RenderSmudge ( topLeft , tileSize , tileScale , smudge , isSmooth , cacheManager ) . Item2 ( graphics ) ;
2020-09-11 23:46:04 +03:00
}
}
}
if ( ( layers & MapLayerFlag . OverlayAll ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( Point location in renderLocations ( ) )
2020-09-11 23:46:04 +03:00
{
2023-06-18 16:56:33 +02:00
Overlay overlay = map . Overlay [ location ] ;
2020-09-11 23:46:04 +03:00
if ( overlay = = null )
{
continue ;
}
2023-03-02 14:56:30 +01:00
if ( Globals . CratesOnTop & & overlay . Type . IsCrate & & ( layers & MapLayerFlag . Overlay ) ! = MapLayerFlag . None )
2023-03-01 17:27:21 +01:00
{
2023-03-02 14:56:30 +01:00
// if "CratesOnTop" logic is active, crates are skipped here and painted afterwards.
2023-03-01 17:27:21 +01:00
continue ;
}
bool paintAsWall = overlay . Type . IsWall & & ( layers & MapLayerFlag . Walls ) ! = MapLayerFlag . None ;
bool paintAsResource = overlay . Type . IsResource & & ( layers & MapLayerFlag . Resources ) ! = MapLayerFlag . None ;
2023-03-02 14:56:30 +01:00
bool paintAsOverlay = overlay . Type . IsOverlay & & ( layers & MapLayerFlag . Overlay ) ! = MapLayerFlag . None ;
if ( paintAsWall | | paintAsResource | | paintAsOverlay )
2020-09-11 23:46:04 +03:00
{
2024-07-29 10:08:56 +02:00
RenderOverlay ( gameInfo , location , map . Bounds , tileSize , tileScale , overlay , false ) . Item2 ( graphics ) ;
2020-09-11 23:46:04 +03:00
}
}
}
if ( ( layers & MapLayerFlag . Buildings ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( ( Point topLeft , Building building ) in map . Buildings . OfType < Building > ( ) )
2020-09-11 23:46:04 +03:00
{
if ( ( locations ! = null ) & & ! locations . Contains ( topLeft ) )
{
continue ;
}
2024-07-29 10:08:56 +02:00
overlappingRenderList . Add ( RenderBuilding ( gameInfo , map , topLeft , tileSize , tileScale , building , false ) ) ;
2020-09-11 23:46:04 +03:00
}
}
if ( ( layers & MapLayerFlag . Infantry ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( ( Point topLeft , InfantryGroup infantryGroup ) in map . Technos . OfType < InfantryGroup > ( ) )
2020-09-11 23:46:04 +03:00
{
if ( ( locations ! = null ) & & ! locations . Contains ( topLeft ) )
{
continue ;
}
2023-07-20 01:07:00 +02:00
foreach ( InfantryStoppingType ist in InfantryGroup . RenderOrder )
2020-09-11 23:46:04 +03:00
{
2023-07-20 01:07:00 +02:00
Infantry infantry = infantryGroup . Infantry [ ( int ) ist ] ;
2020-09-11 23:46:04 +03:00
if ( infantry = = null )
{
continue ;
}
2024-07-29 10:08:56 +02:00
overlappingRenderList . Add ( RenderInfantry ( topLeft , tileSize , infantry , ist , false ) ) ;
2020-09-11 23:46:04 +03:00
}
}
}
if ( ( layers & MapLayerFlag . Units ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( ( Point topLeft , Unit unit ) in map . Technos . OfType < Unit > ( ) )
2020-09-11 23:46:04 +03:00
{
if ( ( locations ! = null ) & & ! locations . Contains ( topLeft ) )
{
continue ;
}
2024-07-29 10:08:56 +02:00
overlappingRenderList . Add ( RenderUnit ( gameInfo , topLeft , tileSize , unit , false ) ) ;
2020-09-11 23:46:04 +03:00
}
}
2023-06-21 12:18:49 +02:00
if ( ( layers & MapLayerFlag . Terrain ) ! = MapLayerFlag . None )
{
foreach ( ( Point topLeft , Terrain terrain ) in map . Technos . OfType < Terrain > ( ) )
{
if ( ( locations ! = null ) & & ! locations . Contains ( topLeft ) )
{
continue ;
}
2024-07-29 10:08:56 +02:00
overlappingRenderList . Add ( RenderTerrain ( topLeft , tileSize , tileScale , terrain , false ) ) ;
2023-06-21 12:18:49 +02:00
}
}
2023-07-20 01:07:00 +02:00
// Paint all the rest
2023-08-03 18:56:02 +02:00
List < RenderInfo > validRenders = overlappingRenderList . Where ( obj = > obj . RenderedObject ! = null ) . ToList ( ) ;
2024-07-29 18:38:08 +02:00
int paintOrder = 0 ;
2023-08-03 18:56:02 +02:00
foreach ( RenderInfo info in validRenders . OrderBy ( obj = > obj . ZOrder ) . ThenBy ( obj = > obj . RenderBasePoint . Y ) . ThenByDescending ( obj = > obj . RenderBasePoint . X ) )
2023-07-20 01:07:00 +02:00
{
info . RenderAction ( graphics ) ;
2024-07-29 18:38:08 +02:00
info . RenderedObject . DrawOrderCache = paintOrder + + ;
2023-07-20 01:07:00 +02:00
}
2023-03-01 17:27:21 +01:00
if ( Globals . CratesOnTop & & ( layers & MapLayerFlag . Overlay ) ! = MapLayerFlag . None )
{
2023-06-18 16:56:33 +02:00
foreach ( Point topLeft in renderLocations ( ) )
2023-03-01 17:27:21 +01:00
{
2023-06-18 16:56:33 +02:00
Overlay overlay = map . Overlay [ topLeft ] ;
2023-03-01 17:27:21 +01:00
if ( overlay = = null | | ! overlay . Type . IsCrate )
{
continue ;
}
2024-07-29 10:08:56 +02:00
RenderOverlay ( gameInfo , topLeft , map . Bounds , tileSize , tileScale , overlay , false ) . Item2 ( graphics ) ;
2023-03-01 17:27:21 +01:00
}
}
2022-09-22 01:26:46 +02:00
if ( ( layers & MapLayerFlag . Waypoints ) ! = MapLayerFlag . None )
{
2022-09-25 12:11:59 +02:00
// todo avoid overlapping waypoints of the same type?
2023-07-27 16:38:56 +02:00
Dictionary < int , int > flagOverlapPoints = new Dictionary < int , int > ( ) ;
2022-09-22 01:26:46 +02:00
HashSet < int > handledPoints = new HashSet < int > ( ) ;
2023-06-19 15:28:04 +02:00
ITeamColor [ ] flagColors = map . FlagColors ;
2024-07-29 18:38:08 +02:00
bool soloMission = map . BasicSection . SoloMission ;
2024-07-29 10:08:56 +02:00
bool previewIsFlag = map . Waypoints . Where ( w = > w . IsPreview & & Waypoint . GetMpIdFromFlag ( w . Flag ) ! = - 1 ) . Any ( ) ;
2024-07-29 18:38:08 +02:00
int lastFlag = - 1 ;
float wpAlpha = 0.5f ;
if ( ! soloMission )
2022-09-22 01:26:46 +02:00
{
2024-07-29 18:38:08 +02:00
int firstFlag = - 1 ;
for ( int i = 0 ; i < map . Waypoints . Length ; i + + )
2023-07-27 16:38:56 +02:00
{
2024-07-29 18:38:08 +02:00
Waypoint waypoint = map . Waypoints [ i ] ;
if ( waypoint . IsPreview )
2023-07-27 16:38:56 +02:00
{
2024-07-29 18:38:08 +02:00
continue ;
}
int mpId = Waypoint . GetMpIdFromFlag ( map . Waypoints [ i ] . Flag ) ;
if ( mpId ! = - 1 )
{
if ( firstFlag = = - 1 )
{
firstFlag = i ;
}
lastFlag = i ;
2023-07-27 16:38:56 +02:00
}
}
2024-07-29 18:38:08 +02:00
// This logic is kind of dirty; it relies on all flag points being in consecutive order. But without that, the preview logic doesn't work.
for ( int i = 0 ; i < firstFlag ; i + + )
2023-07-27 16:38:56 +02:00
{
2024-07-29 18:38:08 +02:00
Waypoint waypoint = map . Waypoints [ i ] ;
if ( ! waypoint . Point . HasValue | | ( locations ! = null & & ! locations . Contains ( waypoint . Point . Value ) )
| | ! map . Metrics . GetCell ( waypoint . Point . Value , out int cell ) | | handledPoints . Contains ( cell ) )
{
continue ;
}
handledPoints . Add ( cell ) ;
2024-08-14 12:53:56 +02:00
RenderWaypoint ( gameInfo , soloMission , tileSize , flagColors , waypoint , wpAlpha , 0 , cacheManager ) . Item2 ( graphics ) ;
2024-07-29 10:08:56 +02:00
}
2024-08-14 12:53:56 +02:00
RenderWaypointFlags ( graphics , gameInfo , map , map . Metrics . Bounds , tileSize , cacheManager ) ;
2024-07-29 10:08:56 +02:00
}
for ( int i = lastFlag + 1 ; i < map . Waypoints . Length ; i + + )
{
Waypoint waypoint = map . Waypoints [ i ] ;
if ( ! waypoint . Point . HasValue | | ( locations ! = null & & ! locations . Contains ( waypoint . Point . Value ) )
| | ! map . Metrics . GetCell ( waypoint . Point . Value , out int cell ) | | handledPoints . Contains ( cell ) )
{
continue ;
2023-07-27 16:38:56 +02:00
}
2024-07-29 10:08:56 +02:00
handledPoints . Add ( cell ) ;
2024-08-14 12:53:56 +02:00
RenderWaypoint ( gameInfo , soloMission , tileSize , flagColors , waypoint , wpAlpha , 0 , cacheManager ) . Item2 ( graphics ) ;
2022-09-22 01:26:46 +02:00
}
}
2024-07-19 15:41:12 +02:00
if ( disposeCacheManager )
{
cacheManager . Reset ( ) ;
}
2020-09-11 23:46:04 +03:00
}
2023-12-02 01:37:52 +01:00
public static void Render ( GameInfo gameInfo , Map map , Graphics graphics , ISet < Point > locations , MapLayerFlag layers )
2020-09-11 23:46:04 +03:00
{
2024-07-19 15:41:12 +02:00
Render ( gameInfo , map , graphics , locations , layers , Globals . MapTileScale , Globals . TheShapeCacheManager ) ;
2020-09-11 23:46:04 +03:00
}
2024-07-29 10:08:56 +02:00
public static ( Rectangle , Action < Graphics > ) RenderSmudge ( Point topLeft , Size tileSize , double tileScale , Smudge smudge , bool isSmoothRendering , ShapeCacheManager cacheManager )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
if ( Globals . FilterTheaterObjects & & ! smudge . Type . ExistsInTheater )
2023-06-27 11:07:57 +02:00
{
Debug . Print ( string . Format ( "Smudge {0} ({1}) not available in this theater." , smudge . Type . Name , smudge . Icon ) ) ;
return ( Rectangle . Empty , ( g ) = > { } ) ;
}
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
Building bld = smudge . AttachedTo ;
if ( bld ! = null )
{
if ( bld . IsPreview )
{
alphaFactor = Globals . PreviewAlphaFloat ;
}
if ( ! bld . IsPrebuilt )
{
alphaFactor * = Globals . UnbuiltAlphaFloat ;
}
}
else if ( smudge . IsPreview )
{
alphaFactor = Globals . PreviewAlphaFloat ;
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
string tileName = "smudge_" + smudge . Type . Name + "_" + smudge . Icon . ToString ( "D4" ) + "_" + tileSize . Width + "x" + tileSize . Height + ( isSmoothRendering ? "_smooth" : String . Empty ) ;
2024-07-19 15:41:12 +02:00
Bitmap tileImg = cacheManager . GetImage ( tileName ) ;
if ( tileImg = = null )
2020-09-11 23:46:04 +03:00
{
2024-07-19 15:41:12 +02:00
bool success = Globals . TheTilesetManager . GetTileData ( smudge . Type . Name , smudge . Icon , out Tile tile , true , false ) ;
if ( tile ! = null & & tile . Image ! = null )
2023-06-19 00:20:44 +02:00
{
2024-07-19 15:41:12 +02:00
Bitmap tileImage = tile . Image ;
// Check if high-quality tile resizing is useful.
2024-07-29 10:08:56 +02:00
if ( ! isSmoothRendering | | ( tileSize . Width = = tileImage . Width & & tileSize . Height = = tileImage . Height ) )
2024-07-19 15:41:12 +02:00
{
tileImg = new Bitmap ( tileSize . Width , tileSize . Height ) ;
Rectangle smudgeBounds = RenderBounds ( tile . Image . Size , new Size ( 1 , 1 ) , tileScale ) ;
using ( Graphics g = Graphics . FromImage ( tileImg ) )
{
2024-07-31 22:33:36 +02:00
SetRenderSettings ( g , isSmoothRendering ) ;
2024-07-19 15:41:12 +02:00
g . DrawImage ( tileImage , smudgeBounds , new Rectangle ( 0 , 0 , tileImage . Width , tileImage . Height ) , GraphicsUnit . Pixel ) ;
}
}
else
{
// Results in a new image that is scaled to the correct size, without edge artifacts.
tileImg = tileImage . HighQualityScale ( tileSize . Width , tileSize . Height ) ;
}
cacheManager . AddImage ( tileName , tileImg ) ;
2023-06-19 00:20:44 +02:00
}
2024-07-19 15:41:12 +02:00
}
if ( tileImg ! = null )
{
Rectangle smudgeBounds = new Rectangle (
( tileSize . Width - tileImg . Width ) / 2 ,
( tileSize . Height - tileImg . Height ) / 2 ,
tileImg . Width , tileImg . Height ) ;
smudgeBounds . X + = topLeft . X * tileSize . Width ;
smudgeBounds . Y + = topLeft . Y * tileSize . Height ;
2020-09-11 23:46:04 +03:00
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alphaFactor ) ) ;
2024-07-19 15:41:12 +02:00
g . DrawImage ( tileImg , smudgeBounds , 0 , 0 , tileImg . Width , tileImg . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2023-07-27 16:38:56 +02:00
}
2020-09-11 23:46:04 +03:00
}
return ( smudgeBounds , render ) ;
}
else
{
Debug . Print ( string . Format ( "Smudge {0} ({1}) not found" , smudge . Type . Name , smudge . Icon ) ) ;
return ( Rectangle . Empty , ( g ) = > { } ) ;
}
}
2024-07-29 10:08:56 +02:00
public static ( Rectangle , Action < Graphics > ) RenderOverlay ( GameInfo gameInfo , Point topLeft , Rectangle ? mapBounds , Size tileSize , double tileScale , Overlay overlay , bool fullOpaque )
2020-09-11 23:46:04 +03:00
{
2023-03-01 17:27:21 +01:00
OverlayType ovtype = overlay . Type ;
2023-05-20 18:40:07 +02:00
string name = ovtype . GraphicsSource ;
2023-03-01 17:27:21 +01:00
int icon = ovtype . IsConcrete | | ovtype . IsResource | | ovtype . IsWall | | ovtype . ForceTileNr = = - 1 ? overlay . Icon : ovtype . ForceTileNr ;
2023-12-02 01:37:52 +01:00
bool isTeleport = gameInfo ! = null & & gameInfo . GameType = = GameType . SoleSurvivor & & ovtype = = SoleSurvivor . OverlayTypes . Teleport & & Globals . AdjustSoleTeleports ;
2023-06-21 12:18:49 +02:00
bool success = Globals . TheTilesetManager . GetTileData ( name , icon , out Tile tile , true , false ) ;
2023-06-19 00:20:44 +02:00
if ( tile ! = null & & tile . Image ! = null )
2020-09-11 23:46:04 +03:00
{
2022-09-25 12:11:59 +02:00
int actualTopLeftX = topLeft . X * tileSize . Width ;
int actualTopLeftY = topLeft . Y * tileSize . Height ;
2022-09-01 13:05:49 +02:00
Rectangle overlayBounds = RenderBounds ( tile . Image . Size , new Size ( 1 , 1 ) , tileScale ) ;
2022-09-25 12:11:59 +02:00
overlayBounds . X + = actualTopLeftX ;
overlayBounds . Y + = actualTopLeftY ;
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( ! fullOpaque & & overlay . IsPreview )
{
alphaFactor = Globals . PreviewAlphaFloat ;
}
Color tint = Color . White ;
if ( overlay . Type . IsResource & & mapBounds . HasValue & & ! mapBounds . Value . Contains ( topLeft ) )
{
tint = Color . FromArgb ( 0xFF , 0x80 , 0x80 ) ;
// Technically the multiplication isn't needed; resources have no preview state in the editor.
if ( ! fullOpaque )
{
alphaFactor * = 0.7f ;
}
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
2020-09-11 23:46:04 +03:00
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2020-09-11 23:46:04 +03:00
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( tint , 1.0f , alphaFactor ) ) ;
2023-07-27 16:38:56 +02:00
g . DrawImage ( tile . Image , overlayBounds , 0 , 0 , tile . Image . Width , tile . Image . Height , GraphicsUnit . Pixel , imageAttributes ) ;
if ( isTeleport )
2022-09-25 12:11:59 +02:00
{
2023-07-27 16:38:56 +02:00
// Transform ROAD tile into the teleport from SS.
int blackBorderX = Math . Max ( 1 , tileSize . Width / 24 ) ;
int blackBorderY = Math . Max ( 1 , tileSize . Height / 24 ) ;
int blueWidth = tileSize . Width - blackBorderX * 2 ;
int blueHeight = tileSize . Height - blackBorderY * 2 ;
int blackWidth = tileSize . Width - blackBorderX * 4 ;
int blackHeight = tileSize . Height - blackBorderY * 4 ;
using ( SolidBrush blue = new SolidBrush ( Color . FromArgb ( 92 , 164 , 200 ) ) )
using ( SolidBrush black = new SolidBrush ( Color . Black ) )
{
g . FillRectangle ( blue , actualTopLeftX + blackBorderX , actualTopLeftY + blackBorderY , blueWidth , blueHeight ) ;
g . FillRectangle ( black , actualTopLeftX + blackBorderX * 2 , actualTopLeftY + blackBorderY * 2 , blackWidth , blackHeight ) ;
}
2022-09-25 12:11:59 +02:00
}
}
2020-09-11 23:46:04 +03:00
}
return ( overlayBounds , render ) ;
}
else
{
2022-08-01 18:20:48 +02:00
Debug . Print ( string . Format ( "Overlay {0} ({1}) not found" , name , icon ) ) ;
2020-09-11 23:46:04 +03:00
return ( Rectangle . Empty , ( g ) = > { } ) ;
}
}
2024-07-29 10:08:56 +02:00
public static RenderInfo RenderTerrain ( Point topLeft , Size tileSize , double tileScale , Terrain terrain , bool fullOpaque )
2022-09-12 15:14:56 +02:00
{
2024-07-03 21:54:25 +02:00
TerrainType type = terrain . Type ;
string tileName = type . GraphicsSource ;
bool succeeded = Globals . TheTilesetManager . GetTileData ( tileName , type . DisplayIcon , out Tile tile , true , false ) ;
2024-08-13 15:34:23 +02:00
terrain . DrawFrameCache = type . DisplayIcon ;
2024-07-03 21:54:25 +02:00
if ( ! succeeded & & ! string . Equals ( type . GraphicsSource , type . Name , StringComparison . InvariantCultureIgnoreCase ) )
2022-09-12 15:14:56 +02:00
{
2024-07-03 21:54:25 +02:00
succeeded = Globals . TheTilesetManager . GetTileData ( type . Name , type . DisplayIcon , out tile , true , false ) ;
2022-09-12 15:14:56 +02:00
}
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( ! fullOpaque )
{
if ( terrain . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
}
2024-07-03 21:54:25 +02:00
Size terrTSize = type . Size ;
2023-06-18 16:56:33 +02:00
Size tileISize = tile . Image . Size ;
Point location = new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ;
2024-07-03 21:54:25 +02:00
Size maxSize = new Size ( terrTSize . Width * tileSize . Width , terrTSize . Height * tileSize . Height ) ;
2023-06-18 16:56:33 +02:00
Rectangle paintBounds ;
if ( ! succeeded )
{
// Stretch dummy graphics over the whole size.
2023-06-21 12:18:49 +02:00
paintBounds = new Rectangle ( 0 , 0 , ( int ) Math . Round ( terrTSize . Width * tileISize . Width * tileScale ) , ( int ) Math . Round ( terrTSize . Height * tileISize . Height * tileScale ) ) ;
2023-06-18 16:56:33 +02:00
}
else
{
paintBounds = RenderBounds ( tileISize , terrTSize , tileScale ) ;
}
2022-09-12 15:14:56 +02:00
paintBounds . X + = location . X ;
paintBounds . Y + = location . Y ;
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alphaFactor ) ) ;
2023-07-27 16:38:56 +02:00
g . DrawImage ( tile . Image , paintBounds , 0 , 0 , tile . Image . Width , tile . Image . Height , GraphicsUnit . Pixel , imageAttributes ) ;
}
2022-09-12 15:14:56 +02:00
}
2023-07-21 13:15:41 +02:00
Point centerPoint = GetTerrainRenderPoint ( terrain ) ;
2023-07-20 01:07:00 +02:00
Point usedCenter = new Point ( topLeft . X * Globals . PixelWidth + centerPoint . X , topLeft . Y * Globals . PixelHeight + centerPoint . Y ) ;
2023-08-03 18:56:02 +02:00
return new RenderInfo ( usedCenter , render , terrain ) ;
2022-09-12 15:14:56 +02:00
}
2024-07-29 10:08:56 +02:00
public static RenderInfo RenderBuilding ( GameInfo gameInfo , Map map , Point topLeft , Size tileSize , double tileScale , Building building , bool fullOpaque )
2020-09-11 23:46:04 +03:00
{
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( ! fullOpaque )
{
if ( building . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
if ( ! building . IsPrebuilt )
{
alphaFactor * = Globals . UnbuiltAlphaFloat ;
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
}
2024-05-10 00:03:03 +02:00
int icon = building . Type . FrameOffset ;
2022-07-14 22:19:01 +02:00
int maxIcon = 0 ;
2022-11-14 23:13:32 +01:00
int damageIconOffs = 0 ;
2022-07-14 22:19:01 +02:00
int collapseIcon = 0 ;
2022-09-29 07:50:41 +02:00
// In TD, damage is when BELOW the threshold. In RA, it's ON the threshold.
2023-12-02 01:37:52 +01:00
int healthyMin = gameInfo . HitPointsGreenMinimum ;
2022-11-14 23:13:32 +01:00
bool isDamaged = building . Strength < = healthyMin ;
bool hasCollapseFrame = false ;
2022-07-14 22:19:01 +02:00
// Only fetch if damaged. BuildingType.IsSingleFrame is an override for the RA mines. Everything else works with one simple logic.
2024-01-31 13:25:32 +01:00
if ( isDamaged & & ! building . Type . IsSingleFrame & & ! building . Type . IsWall )
2022-07-14 22:19:01 +02:00
{
2023-06-14 20:46:54 +02:00
maxIcon = Globals . TheTilesetManager . GetTileDataLength ( building . Type . GraphicsSource ) ;
2023-07-08 15:08:57 +02:00
hasCollapseFrame = maxIcon > 1 & & maxIcon % 2 = = 1 ;
2023-06-16 21:46:44 +02:00
damageIconOffs = ( maxIcon + ( hasCollapseFrame ? 0 : 1 ) ) / 2 ;
2022-11-14 23:13:32 +01:00
collapseIcon = maxIcon - 1 ;
2022-07-14 22:19:01 +02:00
}
if ( building . Type . HasTurret )
{
2022-11-14 23:13:32 +01:00
icon + = BodyShape [ Facing32 [ building . Direction . ID ] ] ;
if ( isDamaged )
2020-09-11 23:46:04 +03:00
{
2022-11-14 23:13:32 +01:00
icon + = damageIconOffs ;
2020-09-11 23:46:04 +03:00
}
2022-07-14 22:19:01 +02:00
}
2024-01-31 13:25:32 +01:00
else if ( building . Type . IsWall )
{
icon + = GetBuildingOverlayIcon ( map , topLeft , building ) ;
}
2022-07-14 22:19:01 +02:00
else
{
2022-11-14 23:13:32 +01:00
if ( building . Strength < = 1 & & hasCollapseFrame )
2020-09-11 23:46:04 +03:00
{
2022-07-14 22:19:01 +02:00
icon = collapseIcon ;
}
2022-11-14 23:13:32 +01:00
else if ( isDamaged )
2022-07-14 22:19:01 +02:00
{
2022-11-14 23:13:32 +01:00
icon + = damageIconOffs ;
2020-09-11 23:46:04 +03:00
}
2022-07-14 22:19:01 +02:00
}
2023-06-30 09:23:31 +02:00
ITeamColor teamColor = building . Type . CanRemap ? Globals . TheTeamColorManager [ building . House ? . BuildingTeamColor ] : null ;
2023-06-18 16:56:33 +02:00
bool succeeded = Globals . TheTilesetManager . GetTeamColorTileData ( building . Type . GraphicsSource , icon , teamColor , out Tile tile , true , false ) ;
2024-08-13 15:34:23 +02:00
building . DrawFrameCache = icon ;
2023-06-18 16:56:33 +02:00
Point location = new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ;
Size maxSize = new Size ( building . Type . Size . Width * tileSize . Width , building . Type . Size . Height * tileSize . Height ) ;
Size bldTSize = building . Type . Size ;
Size tileISize = tile . Image . Size ;
Rectangle paintBounds ;
if ( ! succeeded )
{
// Stretch dummy graphics over the whole size.
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
paintBounds = new Rectangle ( 0 , 0 , maxSize . Width , maxSize . Height ) ;
2023-06-18 16:56:33 +02:00
}
else
{
paintBounds = RenderBounds ( tileISize , bldTSize , tileScale ) ;
}
Rectangle buildingBounds = new Rectangle ( location , maxSize ) ;
Tile factoryOverlayTile = null ;
// Draw no factory overlay over the collapse frame.
if ( building . Type . FactoryOverlay ! = null & & ( building . Strength > 1 | | ! hasCollapseFrame ) )
2020-09-11 23:46:04 +03:00
{
2023-06-18 16:56:33 +02:00
int overlayIcon = 0 ;
if ( building . Strength < = healthyMin )
2020-09-11 23:46:04 +03:00
{
2023-06-18 16:56:33 +02:00
int maxOverlayIcon = Globals . TheTilesetManager . GetTileDataLength ( building . Type . FactoryOverlay ) ;
overlayIcon = maxOverlayIcon / 2 ;
2020-09-11 23:46:04 +03:00
}
2023-06-30 09:23:31 +02:00
Globals . TheTilesetManager . GetTeamColorTileData ( building . Type . FactoryOverlay , overlayIcon , Globals . TheTeamColorManager [ building . House ? . BuildingTeamColor ] , out factoryOverlayTile ) ;
2023-06-18 16:56:33 +02:00
}
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2020-09-11 23:46:04 +03:00
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alphaFactor ) ) ;
2023-07-27 16:38:56 +02:00
if ( factoryOverlayTile ! = null )
2020-09-11 23:46:04 +03:00
{
2023-07-27 16:38:56 +02:00
// Avoid factory overlay showing as semitransparent.
using ( Bitmap factory = new Bitmap ( maxSize . Width , maxSize . Height ) )
2022-08-11 22:49:22 +02:00
{
2023-07-27 16:38:56 +02:00
factory . SetResolution ( 96 , 96 ) ;
using ( Graphics factoryG = Graphics . FromImage ( factory ) )
2023-06-19 00:20:44 +02:00
{
2023-07-27 16:38:56 +02:00
factoryG . CopyRenderSettingsFrom ( g ) ;
Size renderSize = tileISize ;
if ( ! succeeded )
{
renderSize . Width = building . Type . Size . Width * tileSize . Width ;
renderSize . Height = building . Type . Size . Height * tileSize . Height ;
}
Rectangle factBounds = RenderBounds ( renderSize , building . Type . Size , tileScale ) ;
Rectangle ovrlBounds = RenderBounds ( factoryOverlayTile . Image . Size , building . Type . Size , tileScale ) ;
factoryG . DrawImage ( tile . Image , factBounds , 0 , 0 , tile . Image . Width , tile . Image . Height , GraphicsUnit . Pixel ) ;
factoryG . DrawImage ( factoryOverlayTile . Image , ovrlBounds , 0 , 0 , factoryOverlayTile . Image . Width , factoryOverlayTile . Image . Height , GraphicsUnit . Pixel ) ;
2023-06-19 00:20:44 +02:00
}
2023-07-27 16:38:56 +02:00
g . DrawImage ( factory , buildingBounds , 0 , 0 , factory . Width , factory . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2022-08-11 22:49:22 +02:00
}
2020-09-11 23:46:04 +03:00
}
2023-07-27 16:38:56 +02:00
else
{
paintBounds . X + = location . X ;
paintBounds . Y + = location . Y ;
g . DrawImage ( tile . Image , paintBounds , 0 , 0 , tile . Image . Width , tile . Image . Height , GraphicsUnit . Pixel , imageAttributes ) ;
}
2023-06-18 16:56:33 +02:00
}
2022-08-01 18:20:48 +02:00
}
2023-07-21 13:15:41 +02:00
Point centerPoint = GetBuildingRenderPoint ( building ) ;
2023-07-20 01:07:00 +02:00
Point usedCenter = new Point ( topLeft . X * Globals . PixelWidth + centerPoint . X , topLeft . Y * Globals . PixelHeight + centerPoint . Y ) ;
2023-08-03 18:56:02 +02:00
// "Z-Order" is for sorting buildings as floor level (0), flat on the floor (5), or sticking out of the floor (default; 10).
// It determines whether pieces on unoccupied cells should overlap objects on these cells or be drawn below them.
return new RenderInfo ( usedCenter , render , building . Type . ZOrder , building ) ;
2020-09-11 23:46:04 +03:00
}
2024-01-31 13:25:32 +01:00
private static int GetBuildingOverlayIcon ( Map map , Point topLeft , Building building )
{
if ( ! building . Type . IsWall | | map = = null )
{
return 0 ;
}
BuildingType bt = building . Type ;
bool hasNorthWall = ( map . Metrics . Adjacent ( topLeft , FacingType . North , out Point north ) ? map . Buildings [ north ] as Building : null ) ? . Type = = bt ;
bool hasEastWall = ( map . Metrics . Adjacent ( topLeft , FacingType . East , out Point east ) ? map . Buildings [ east ] as Building : null ) ? . Type = = bt ;
bool hasSouthWall = ( map . Metrics . Adjacent ( topLeft , FacingType . South , out Point south ) ? map . Buildings [ south ] as Building : null ) ? . Type = = bt ;
bool hasWestWall = ( map . Metrics . Adjacent ( topLeft , FacingType . West , out Point west ) ? map . Buildings [ west ] as Building : null ) ? . Type = = bt ;
2024-05-10 00:03:03 +02:00
string btName = bt . Name ;
2024-01-31 13:25:32 +01:00
hasNorthWall | = map . Overlay . Adjacent ( topLeft , FacingType . North ) ? . Type . Name = = btName ;
hasEastWall | = map . Overlay . Adjacent ( topLeft , FacingType . East ) ? . Type . Name = = btName ;
hasSouthWall | = map . Overlay . Adjacent ( topLeft , FacingType . South ) ? . Type . Name = = btName ;
hasWestWall | = map . Overlay . Adjacent ( topLeft , FacingType . West ) ? . Type . Name = = btName ;
int icon = 0 ;
if ( hasNorthWall )
{
icon | = 1 ;
}
if ( hasEastWall )
{
icon | = 2 ;
}
if ( hasSouthWall )
{
icon | = 4 ;
}
if ( hasWestWall )
{
icon | = 8 ;
}
return icon ;
}
2024-07-29 10:08:56 +02:00
public static RenderInfo RenderInfantry ( Point topLeft , Size tileSize , Infantry infantry , InfantryStoppingType infantryStoppingType , bool fullOpaque )
2020-09-11 23:46:04 +03:00
{
2024-05-10 00:03:03 +02:00
int icon = HumanShape [ Facing32 [ infantry . Direction . ID ] ] ;
2023-06-16 21:46:44 +02:00
ITeamColor teamColor = infantry . Type . CanRemap ? Globals . TheTeamColorManager [ infantry . House ? . UnitTeamColor ] : null ;
Tile tile = null ;
2023-10-11 17:33:51 +02:00
// InfantryType.Init() should have taken care of RA's classic civilian remap mess at this point, and remapped all cached source graphics.
2024-02-03 23:30:51 +01:00
bool success = Globals . TheTilesetManager . GetTeamColorTileData ( infantry . Type . GraphicsSource , icon , teamColor , out tile , true , false ) ;
2023-07-20 01:07:00 +02:00
if ( tile = = null | | tile . Image = = null )
{
2024-02-03 23:33:53 +01:00
Debug . Print ( string . Format ( "Infantry {0} graphics ({1}, frame {2}) not found" , infantry . Type . Name , infantry . Type . GraphicsSource , icon ) ) ;
2023-08-03 18:56:02 +02:00
return new RenderInfo ( Point . Empty , ( g ) = > { } , infantry ) ;
2023-07-20 01:07:00 +02:00
}
Size imSize = tile . Image . Size ;
Point origLocation = new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ;
Point renderLocation = origLocation ;
2024-04-16 18:38:07 +02:00
Point offset ;
2023-07-20 01:07:00 +02:00
// Offset is calculated as "pixels" in the old 24-pixel cells.
Size renderSize ;
if ( success )
{
2024-04-16 18:38:07 +02:00
// Actual graphics: get tweaked positions accounting for the shape of the infantry and the fact the theoretical positions are at their feet.
offset = GetInfantryRenderPoint ( infantryStoppingType ) ;
2023-07-20 01:07:00 +02:00
Point offsetActual = new Point ( offset . X * tileSize . Width / Globals . PixelWidth , offset . Y * tileSize . Height / Globals . PixelHeight ) ;
renderLocation . Offset ( offsetActual ) ;
renderSize = new Size ( imSize . Width * tileSize . Width / Globals . OriginalTileWidth , imSize . Height * tileSize . Height / Globals . OriginalTileHeight ) ;
}
else
2020-09-11 23:46:04 +03:00
{
2024-04-16 18:38:07 +02:00
// Dummy graphics: use theoretical positions.
offset = InfantryGroup . RenderPosition ( infantryStoppingType , false ) ;
Point offsetActual = new Point ( offset . X * tileSize . Width / Globals . PixelWidth , offset . Y * tileSize . Height / Globals . PixelHeight ) ;
2023-07-20 01:07:00 +02:00
renderLocation . Offset ( offsetActual ) ;
renderSize = new Size ( imSize . Width * tileSize . Width / Globals . OriginalTileWidth , imSize . Height * tileSize . Height / Globals . OriginalTileHeight ) ;
renderSize . Width / = 3 ;
renderSize . Height / = 2 ;
}
Rectangle renderBounds = new Rectangle ( renderLocation - new Size ( renderSize . Width / 2 , renderSize . Height / 2 ) , renderSize ) ;
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( ! fullOpaque )
{
if ( infantry . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
}
2023-07-20 01:07:00 +02:00
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2023-07-20 01:07:00 +02:00
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alphaFactor ) ) ;
2023-07-27 16:38:56 +02:00
g . DrawImage ( tile . Image , renderBounds , 0 , 0 , tile . Image . Width , tile . Image . Height , GraphicsUnit . Pixel , imageAttributes ) ;
// Test code to visualise original 5-point die face location (green), and corrected infantry base point (feet) location (red).
/ * /
Size pixel = new Size ( tileSize . Width / Globals . PixelWidth , tileSize . Height / Globals . PixelHeight ) ;
using ( SolidBrush sb = new SolidBrush ( Color . Red ) )
{
g . FillRectangle ( sb , new Rectangle ( renderLocation , pixel ) ) ;
}
using ( SolidBrush sb = new SolidBrush ( Color . LimeGreen ) )
{
g . FillRectangle ( sb , new Rectangle ( new Point (
origLocation . X + offsetBare . X * tileSize . Width / Globals . PixelWidth ,
origLocation . Y + ( offsetBare . Y * tileSize . Height / Globals . PixelHeight ) ) , pixel ) ) ;
}
//*/
2020-09-11 23:46:04 +03:00
}
}
2023-07-20 01:07:00 +02:00
// Render position is the feet point, adjusted to 24-pixel cell location.
2023-08-03 18:56:02 +02:00
return new RenderInfo ( new Point ( topLeft . X * Globals . PixelWidth + offset . X , topLeft . Y * Globals . PixelHeight + offset . Y ) , render , infantry ) ;
2020-09-11 23:46:04 +03:00
}
2024-07-29 10:08:56 +02:00
public static RenderInfo RenderUnit ( GameInfo gameInfo , Point topLeft , Size tileSize , Unit unit , bool fullOpaque )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
int icon = 0 ;
int bodyFrames = 0 ;
// In TD, damage is when BELOW the threshold. In RA, it's ON the threshold.
2023-12-02 01:37:52 +01:00
int healthyMin = gameInfo . HitPointsGreenMinimum ;
int damagedMin = gameInfo . HitPointsYellowMinimum ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
FrameUsage frameUsage = unit . Type . BodyFrameUsage ;
2024-06-10 18:29:23 +02:00
if ( frameUsage . HasFlag ( FrameUsage . Frames01Single ) )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
icon = 0 ;
// Not actually determined, but whatever. Single frame units generally have no turret.
bodyFrames = 1 ;
}
2024-06-10 18:29:23 +02:00
else if ( frameUsage . HasFlag ( FrameUsage . Frames08Cardinal ) )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
icon = ( ( BodyShape [ Facing32 [ unit . Direction . ID ] ] + 2 ) / 4 ) & 0x07 ;
bodyFrames = 8 ;
}
2024-06-10 18:29:23 +02:00
else if ( frameUsage . HasFlag ( FrameUsage . Frames16Simple ) )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
icon = BodyShape [ Facing16 [ unit . Direction . ID ] * 2 ] / 2 ;
bodyFrames = 16 ;
}
2024-06-10 18:29:23 +02:00
else if ( frameUsage . HasFlag ( FrameUsage . Frames16Symmetrical ) )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
// Special case for 16-frame rotation saved as 8-frame because it is symmetrical and thus the second half of the frames is the same.
2024-06-10 18:29:23 +02:00
icon = ( BodyShape [ Facing32 [ unit . Direction . ID ] ] / 2 ) & 0x07 ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
bodyFrames = 8 ;
2020-09-11 23:46:04 +03:00
}
2024-06-10 18:29:23 +02:00
else if ( frameUsage . HasFlag ( FrameUsage . Frames32Full ) | | ! frameUsage . HasAnyFlags ( FrameUsage . FrameUsages ) )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
icon = BodyShape [ Facing32 [ unit . Direction . ID ] ] ;
bodyFrames = 32 ;
}
// Special logic for TD gunboat's damaged states.
// East facing is not actually possible to set in missions. This is just the turret facing.
2024-06-10 18:29:23 +02:00
if ( frameUsage . HasFlag ( FrameUsage . DamageStates ) )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
if ( unit . Strength < = healthyMin )
icon + = bodyFrames ;
if ( unit . Strength < = damagedMin )
icon + = bodyFrames ;
// Skip three-step damaged frames. In practice this will just go to the east-facing ones though.
bodyFrames * = 3 ;
}
// Special logic for APC-types with unload frames.
if ( ( frameUsage & FrameUsage . HasUnloadFrames ) ! = FrameUsage . None )
{
2024-06-10 09:05:42 +02:00
if ( unit . Type . IsAircraft )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// Transport heli unload has 4 frames
bodyFrames + = 4 ;
2020-09-11 23:46:04 +03:00
}
2024-06-10 09:05:42 +02:00
else if ( unit . Type . IsVessel )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// Boat unload has 4 frames
bodyFrames + = 4 ;
2020-09-11 23:46:04 +03:00
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
else
2022-11-27 20:48:33 +01:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// APC unload has 6 frames.
bodyFrames + = 6 ;
2020-09-11 23:46:04 +03:00
}
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// Get House color.
2023-06-16 21:46:44 +02:00
ITeamColor teamColor = null ;
if ( unit . House ! = null & & unit . Type . CanRemap )
2020-09-11 23:46:04 +03:00
{
2024-05-10 00:03:03 +02:00
string teamColorName ;
2023-06-16 21:46:44 +02:00
if ( ! unit . House . OverrideTeamColors . TryGetValue ( unit . Type . Name , out teamColorName ) )
2020-09-11 23:46:04 +03:00
{
2023-06-30 09:23:31 +02:00
teamColorName = unit . House ? . UnitTeamColor ;
2020-09-11 23:46:04 +03:00
}
2023-06-16 21:46:44 +02:00
teamColor = Globals . TheTeamColorManager [ teamColorName ] ;
2020-09-11 23:46:04 +03:00
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// Get body frame
2023-06-16 21:46:44 +02:00
Globals . TheTilesetManager . GetTeamColorTileData ( unit . Type . Name , icon , teamColor , out Tile tile , true , false ) ;
2024-08-13 15:34:23 +02:00
unit . DrawFrameCache = icon ;
2023-06-16 21:46:44 +02:00
if ( tile = = null | | tile . Image = = null )
2022-11-27 20:48:33 +01:00
{
Debug . Print ( string . Format ( "Unit {0} ({1}) not found" , unit . Type . Name , icon ) ) ;
2023-08-03 18:56:02 +02:00
return new RenderInfo ( Point . Empty , ( g ) = > { } , null ) ;
2022-11-27 20:48:33 +01:00
}
2023-06-18 16:56:33 +02:00
Size imSize = tile . Image . Size ;
Point location =
2022-11-27 20:48:33 +01:00
new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) +
new Size ( tileSize . Width / 2 , tileSize . Height / 2 ) ;
2023-06-18 16:56:33 +02:00
Size renderSize = new Size ( imSize . Width * tileSize . Width / Globals . OriginalTileWidth , imSize . Height * tileSize . Height / Globals . OriginalTileHeight ) ;
Rectangle renderRect = new Rectangle ( new Point ( 0 , 0 ) , renderSize ) ;
Rectangle renderBounds = new Rectangle ( location - new Size ( renderSize . Width / 2 , renderSize . Height / 2 ) , renderSize ) ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// Turret handling
2022-11-27 20:48:33 +01:00
Tile turretTile = null ;
Tile turret2Tile = null ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
Point turretAdjust = Point . Empty ;
Point turret2Adjust = Point . Empty ;
2022-11-27 20:48:33 +01:00
if ( unit . Type . HasTurret )
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
FrameUsage turrUsage = unit . Type . TurretFrameUsage ;
2022-11-27 20:48:33 +01:00
string turretName = unit . Type . Turret ? ? unit . Type . Name ;
string turret2Name = unit . Type . HasDoubleTurret ? unit . Type . SecondTurret ? ? unit . Type . Turret ? ? unit . Type . Name : null ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
int turret1Icon = 0 ;
int turret2Icon = 0 ;
if ( ( turrUsage & FrameUsage . Frames01Single ) ! = FrameUsage . None )
2020-09-11 23:46:04 +03:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
turret1Icon = 0 ;
turret2Icon = 0 ;
2022-11-27 20:48:33 +01:00
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
else if ( ( turrUsage & FrameUsage . Frames08Cardinal ) ! = FrameUsage . None )
2022-11-27 20:48:33 +01:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
// Never used for a turret, but whatever.
turret1Icon = ( ( BodyShape [ Facing32 [ unit . Direction . ID ] ] + 2 ) / 4 ) & 0x07 ;
turret2Icon = turret1Icon ;
2022-11-27 20:48:33 +01:00
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
else if ( ( turrUsage & FrameUsage . Frames16Simple ) ! = FrameUsage . None )
2022-11-27 20:48:33 +01:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
turret1Icon = BodyShape [ Facing16 [ unit . Direction . ID ] * 2 ] / 2 ;
turret2Icon = turret1Icon ;
}
else if ( ( turrUsage & FrameUsage . Frames16Symmetrical ) ! = FrameUsage . None )
{
// Special case for 16-frame rotation saved as 8-frame because it is symmetrical and thus the second half of the frames is the same (MGG)
turret1Icon = ( BodyShape [ Facing32 [ unit . Direction . ID ] ] / 2 ) & 7 ;
turret2Icon = turret1Icon ;
}
else if ( ( turrUsage & FrameUsage . Frames32Full ) ! = FrameUsage . None )
{
turret1Icon = BodyShape [ Facing32 [ unit . Direction . ID ] ] ;
turret2Icon = turret1Icon ;
2020-09-11 23:46:04 +03:00
}
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
else if ( ( turrUsage & FrameUsage . Rotor ) ! = FrameUsage . None )
{
turret1Icon = ( unit . Direction . ID > > 5 ) % 2 = = 1 ? 9 : 5 ;
turret2Icon = ( unit . Direction . ID > > 5 ) % 2 = = 1 ? 8 : 4 ;
}
// If same as body name, add body frames.
turret1Icon = unit . Type . Name . Equals ( turretName , StringComparison . OrdinalIgnoreCase ) ? bodyFrames + turret1Icon : turret1Icon ;
turret2Icon = unit . Type . Name . Equals ( turret2Name , StringComparison . OrdinalIgnoreCase ) ? bodyFrames + turret2Icon : turret2Icon ;
2022-11-27 20:48:33 +01:00
if ( turretName ! = null )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
Globals . TheTilesetManager . GetTeamColorTileData ( turretName , turret1Icon , teamColor , out turretTile , false , false ) ;
2022-11-27 20:48:33 +01:00
if ( turret2Name ! = null )
2023-06-18 16:56:33 +02:00
Globals . TheTilesetManager . GetTeamColorTileData ( turret2Name , turret2Icon , teamColor , out turret2Tile , false , false ) ;
2024-06-10 18:29:23 +02:00
// Flatbed is a special case; if it is used, TurretOffset is ignored.
if ( turrUsage . HasFlag ( FrameUsage . OnFlatBed ) )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
2024-03-29 11:00:09 +01:00
// OnFlatBed indicates the turret oFfset is determined by the BackTurretAdjust table.
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
turretAdjust = BackTurretAdjust [ Facing32 [ unit . Direction . ID ] ] ;
// Never actually used for 2 turrets. Put second turret in the front?
turret2Adjust = BackTurretAdjust [ Facing32 [ ( byte ) ( ( unit . Direction . ID + DirectionTypes . South . ID ) & 0xFF ) ] ] ;
}
2024-06-10 18:29:23 +02:00
else if ( unit . Type . TurretOffset ! = 0 )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
// Used by ships and by the transport helicopter.
int distance = unit . Type . TurretOffset ;
int face = ( unit . Direction . ID > > 5 ) & 7 ;
2024-06-10 18:29:23 +02:00
if ( turrUsage . HasFlag ( FrameUsage . Rotor ) )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
// Rotor stretch distance is given by a table.
distance * = HeliDistanceAdjust [ face ] ;
}
int x = 0 ;
int y = 0 ;
// For vessels, perspective stretch is simply done as '/ 2'.
int perspectiveDivide = unit . Type . IsVessel ? 2 : 1 ;
MovePoint ( ref x , ref y , unit . Direction . ID , distance , perspectiveDivide ) ;
turretAdjust . X + = x ;
turretAdjust . Y + = y ;
if ( unit . Type . HasDoubleTurret )
{
x = 0 ;
y = 0 ;
MovePoint ( ref x , ref y , ( byte ) ( ( unit . Direction . ID + DirectionTypes . South . ID ) & 0xFF ) , distance , perspectiveDivide ) ;
turret2Adjust . X + = x ;
turret2Adjust . Y + = y ;
}
}
// Adjust Y-offset.
turretAdjust . Y + = unit . Type . TurretY ;
turret2Adjust . Y + = unit . Type . TurretY ;
2022-11-27 20:48:33 +01:00
}
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( ! fullOpaque )
{
if ( unit . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
}
2022-11-27 20:48:33 +01:00
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2020-09-11 23:46:04 +03:00
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alphaFactor ) ) ;
2023-07-27 16:38:56 +02:00
// Combine body and turret to one image, then paint it. This is done because it might be semitransparent.
using ( Bitmap unitBm = new Bitmap ( renderBounds . Width , renderBounds . Height ) )
2020-09-11 23:46:04 +03:00
{
2023-07-27 16:38:56 +02:00
unitBm . SetResolution ( 96 , 96 ) ;
using ( Graphics unitG = Graphics . FromImage ( unitBm ) )
2020-09-11 23:46:04 +03:00
{
2023-07-27 16:38:56 +02:00
unitG . CopyRenderSettingsFrom ( g ) ;
if ( tile ! = null )
2020-09-11 23:46:04 +03:00
{
2023-07-27 16:38:56 +02:00
unitG . DrawImage ( tile . Image , renderRect , 0 , 0 , tile . Image . Width , tile . Image . Height , GraphicsUnit . Pixel ) ;
2020-09-11 23:46:04 +03:00
}
2023-07-27 16:38:56 +02:00
if ( unit . Type . HasTurret )
2020-09-11 23:46:04 +03:00
{
2023-07-27 16:38:56 +02:00
Point center = new Point ( renderBounds . Width / 2 , renderBounds . Height / 2 ) ;
2023-05-31 17:45:31 +02:00
2023-07-27 16:38:56 +02:00
void RenderTurret ( Graphics ug , Tile turrTile , Point turrAdjust , Size tSize )
{
Size turretSize = turrTile . Image . Size ;
Size turretRenderSize = new Size ( turretSize . Width * tSize . Width / Globals . OriginalTileWidth , turretSize . Height * tSize . Height / Globals . OriginalTileHeight ) ;
Rectangle turrBounds = new Rectangle ( center - new Size ( turretRenderSize . Width / 2 , turretRenderSize . Height / 2 ) , turretRenderSize ) ;
turrBounds . Offset (
turrAdjust . X * tSize . Width / Globals . PixelWidth ,
turrAdjust . Y * tSize . Height / Globals . PixelHeight
) ;
ug . DrawImage ( turrTile . Image , turrBounds , 0 , 0 , turrTile . Image . Width , turrTile . Image . Height , GraphicsUnit . Pixel ) ;
}
2023-05-31 17:45:31 +02:00
2023-07-27 16:38:56 +02:00
if ( turretTile ! = null & & turretTile . Image ! = null )
{
RenderTurret ( unitG , turretTile , turretAdjust , tileSize ) ;
}
if ( unit . Type . HasDoubleTurret & & turret2Tile ! = null & & turret2Tile . Image ! = null )
{
RenderTurret ( unitG , turret2Tile , turret2Adjust , tileSize ) ;
}
2022-09-10 02:47:12 +02:00
}
2020-09-11 23:46:04 +03:00
}
2023-07-27 16:38:56 +02:00
g . DrawImage ( unitBm , renderBounds , 0 , 0 , renderBounds . Width , renderBounds . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2020-09-11 23:46:04 +03:00
}
}
}
2023-07-21 13:15:41 +02:00
Point centerPoint = GetVehicleRenderPoint ( ) ;
Point usedCenter = new Point ( topLeft . X * Globals . PixelWidth + centerPoint . X , topLeft . Y * Globals . PixelHeight + centerPoint . Y ) ;
2023-08-03 18:56:02 +02:00
return new RenderInfo ( usedCenter , render , unit ) ;
2020-09-11 23:46:04 +03:00
}
2022-08-11 22:49:22 +02:00
2024-08-14 12:53:56 +02:00
public static ( Rectangle , Action < Graphics > ) RenderWaypoint ( GameInfo gameInfo , bool soloMission , Size tileSize , ITeamColor [ ] flagColors , Waypoint waypoint ,
float alphaFactor , int offset , ShapeCacheManager cacheManager )
2022-09-22 01:26:46 +02:00
{
if ( ! waypoint . Point . HasValue )
{
return ( Rectangle . Empty , ( g ) = > { } ) ;
}
Point point = waypoint . Point . Value ;
2023-06-27 11:07:57 +02:00
bool isDefaultIcon = true ;
2024-08-14 12:53:56 +02:00
string tileGraphics = "trans.icn" ;
int icon = 3 ;
2023-04-25 18:50:13 +02:00
ITeamColor teamColor = null ;
2023-06-27 11:07:57 +02:00
double sizeMultiplier = 1 ;
2024-07-29 10:08:56 +02:00
if ( waypoint . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
alphaFactor = alphaFactor . Restrict ( 0 , 1 ) ;
2024-08-14 12:53:56 +02:00
int mpId = soloMission ? - 1 : Waypoint . GetMpIdFromFlag ( waypoint . Flag ) ;
Bitmap image = null ;
if ( waypoint . Flag . HasFlag ( WaypointFlag . CrateSpawn ) )
2022-09-22 01:26:46 +02:00
{
2023-06-14 20:46:54 +02:00
isDefaultIcon = false ;
2022-09-22 01:26:46 +02:00
tileGraphics = "scrate" ;
2023-06-27 11:07:57 +02:00
icon = 0 ;
2023-06-19 15:28:04 +02:00
sizeMultiplier = 2 ;
2022-09-22 01:26:46 +02:00
}
2024-08-14 12:53:56 +02:00
else if ( mpId > = 0 & & mpId < flagColors . Length )
2023-06-14 20:46:54 +02:00
{
2024-08-14 12:53:56 +02:00
isDefaultIcon = false ;
tileGraphics = "flagfly" ;
icon = 0 ;
teamColor = flagColors [ mpId ] ;
// Always paint flags as opaque.
//transparencyModifier = 1.0f;
2023-06-14 20:46:54 +02:00
}
2024-08-14 12:53:56 +02:00
string id = "waypoint_" + tileGraphics + "_icn" + icon + "_x" + sizeMultiplier + "_mpid" + mpId ;
image = cacheManager . GetImage ( id ) ;
if ( image = = null )
2022-11-27 20:48:33 +01:00
{
2024-08-14 12:53:56 +02:00
bool gotTile = Globals . TheTilesetManager . GetTeamColorTileData ( tileGraphics , icon , teamColor , out Tile tile ) ;
if ( gotTile )
{
image = new Bitmap ( tile . Image ) ;
}
else if ( isDefaultIcon )
{
using ( Bitmap selectCursor = Globals . TheTilesetManager . GetTexture ( @"DATA\ART\TEXTURES\SRGB\ICON_SELECT_FRIENDLY_X2_00.DDS" , tileGraphics , icon , true ) )
{
Rectangle opaqueBounds = ImageUtils . CalculateOpaqueBounds ( selectCursor ) ;
image = selectCursor . FitToBoundingBox ( opaqueBounds , Globals . OriginalTileHeight , Globals . OriginalTileHeight , Color . Transparent ) ;
}
}
if ( image ! = null )
{
cacheManager . AddImage ( id , image ) ;
}
2022-11-27 20:48:33 +01:00
}
2024-08-14 12:53:56 +02:00
if ( image = = null )
2022-11-27 20:48:33 +01:00
{
Debug . Print ( string . Format ( "Waypoint graphics {0} ({1}) not found" , tileGraphics , icon ) ) ;
return ( Rectangle . Empty , ( g ) = > { } ) ;
}
2023-06-18 16:56:33 +02:00
Point location = new Point ( point . X * tileSize . Width , point . Y * tileSize . Height ) ;
2024-08-14 12:53:56 +02:00
Size renderSize = new Size ( image . Width * tileSize . Width / Globals . OriginalTileWidth , image . Height * tileSize . Height / Globals . OriginalTileHeight ) ;
2023-06-21 12:18:49 +02:00
renderSize . Width = ( int ) Math . Round ( renderSize . Width * sizeMultiplier ) ;
renderSize . Height = ( int ) Math . Round ( renderSize . Height * sizeMultiplier ) ;
2023-06-19 15:28:04 +02:00
Rectangle renderBounds = new Rectangle ( location , renderSize ) ;
2024-08-14 12:53:56 +02:00
Rectangle imgBounds = new Rectangle ( Point . Empty , image . Size ) ;
2023-06-19 15:28:04 +02:00
bool isClipping = renderSize . Width > tileSize . Width | | renderSize . Height > tileSize . Height ;
if ( tileSize . Width > renderSize . Width )
{
2023-06-21 12:18:49 +02:00
// Pad. This rounds upwards because bottom and left are generally shadows.
renderBounds . X + = ( int ) Math . Round ( ( tileSize . Width - renderSize . Width ) / 2.0 , MidpointRounding . AwayFromZero ) ;
2023-06-19 15:28:04 +02:00
}
else if ( tileSize . Width < renderSize . Width )
{
// Crop
renderBounds . Width = tileSize . Width ;
2023-06-21 12:18:49 +02:00
imgBounds . Width = ( int ) Math . Round ( tileSize . Width / sizeMultiplier ) ;
2024-08-14 12:53:56 +02:00
imgBounds . X = ( image . Width - imgBounds . Width ) / 2 ;
2023-06-19 15:28:04 +02:00
}
if ( tileSize . Height > renderSize . Height )
{
2023-06-21 12:18:49 +02:00
// Pad. This rounds upwards because bottom and left are generally shadows.
renderBounds . Y + = ( int ) Math . Round ( ( tileSize . Height - renderSize . Height ) / 2.0 , MidpointRounding . AwayFromZero ) ;
2023-06-19 15:28:04 +02:00
}
else if ( tileSize . Height < renderSize . Height )
{
// Crop
renderBounds . Height = tileSize . Height ;
2023-06-21 12:18:49 +02:00
imgBounds . Height = ( int ) Math . Round ( tileSize . Height / sizeMultiplier ) ;
2024-08-14 12:53:56 +02:00
imgBounds . Y = ( image . Height - imgBounds . Height ) / 2 ;
2023-06-19 15:28:04 +02:00
}
2023-07-27 16:38:56 +02:00
// Apply offset
int actualOffsetX = offset * tileSize . Width / Globals . PixelWidth ;
int actualOffsetY = offset * tileSize . Height / Globals . PixelHeight ;
renderBounds . X + = actualOffsetX ;
renderBounds . Y + = actualOffsetY ;
renderBounds . Width = Math . Max ( 0 , renderBounds . Width - actualOffsetX ) ;
renderBounds . Height = Math . Max ( 0 , renderBounds . Height - actualOffsetY ) ;
2022-11-27 20:48:33 +01:00
void render ( Graphics g )
{
2023-07-27 16:38:56 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2022-09-22 01:26:46 +02:00
{
2024-07-29 10:08:56 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alphaFactor ) ) ;
2023-07-27 16:38:56 +02:00
if ( renderBounds . Width > 0 & & renderBounds . Height > 0 & & imgBounds . Width > 0 & & imgBounds . Height > 0 )
2022-09-22 01:26:46 +02:00
{
2024-08-14 12:53:56 +02:00
g . DrawImage ( image , renderBounds , imgBounds . X , imgBounds . Y , imgBounds . Width , imgBounds . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2023-07-27 16:38:56 +02:00
}
2022-09-22 01:26:46 +02:00
}
}
2022-11-27 20:48:33 +01:00
return ( renderBounds , render ) ;
2022-09-22 01:26:46 +02:00
}
2023-07-21 13:15:41 +02:00
private static Point GetVehicleRenderPoint ( )
{
return new Point ( Globals . PixelWidth / 2 , Globals . PixelHeight / 2 ) ;
}
private static Point GetInfantryRenderPoint ( InfantryStoppingType ist )
{
return InfantryGroup . RenderPosition ( ist , true ) ;
}
private static Point GetTerrainRenderPoint ( Terrain terrain )
{
return terrain . Type . CenterPoint ;
}
private static Point GetBuildingRenderPoint ( Building building )
{
return GeneralUtils . GetOccupiedCenter ( building . Type . BaseOccupyMask , new Size ( Globals . PixelWidth , Globals . PixelHeight ) ) ;
}
2023-06-23 02:35:01 +02:00
public static void RenderAllBoundsFromCell < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( int , T ) > renderList , CellMetrics metrics )
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
RenderAllBoundsFromCell ( graphics , visibleCells , tileSize , renderList , metrics , Color . Green ) ;
2022-09-13 10:44:15 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderAllBoundsFromCell < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( int , T ) > renderList , CellMetrics metrics , Color boundsColor )
2022-09-22 01:26:46 +02:00
{
2023-06-23 02:35:01 +02:00
RenderAllBoundsFromCell ( graphics , visibleCells , tileSize , renderList . Select ( tp = > tp . Item1 ) , metrics , boundsColor ) ;
2022-09-22 01:26:46 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderAllBoundsFromCell ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < int > renderList , CellMetrics metrics , Color boundsColor )
2022-09-13 10:44:15 +02:00
{
2023-06-18 16:56:33 +02:00
using ( Pen boundsPen = new Pen ( boundsColor , Math . Max ( 1 , tileSize . Width / 16.0f ) ) )
2022-09-13 10:44:15 +02:00
{
2024-05-10 00:03:03 +02:00
foreach ( int cell in renderList )
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
if ( metrics . GetLocation ( cell , out Point topLeft ) & & visibleCells . Contains ( topLeft ) )
{
Rectangle bounds = new Rectangle ( new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) , tileSize ) ;
graphics . DrawRectangle ( boundsPen , bounds ) ;
}
2022-09-13 10:44:15 +02:00
}
}
}
2023-06-23 02:35:01 +02:00
public static void RenderAllBoundsFromPoint < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( Point , T ) > renderList )
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
RenderAllBoundsFromPoint ( graphics , visibleCells , tileSize , renderList . Select ( tp = > tp . Item1 ) , Color . Green ) ;
2022-09-22 01:26:46 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderAllBoundsFromPoint ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < Point > renderList , Color boundsColor )
2022-09-22 01:26:46 +02:00
{
2023-06-18 16:56:33 +02:00
using ( Pen boundsPen = new Pen ( boundsColor , Math . Max ( 1 , tileSize . Width / 16.0f ) ) )
2022-09-22 01:26:46 +02:00
{
2023-06-23 02:35:01 +02:00
foreach ( Point topLeft in renderList . Where ( pt = > visibleCells . Contains ( pt ) ) )
2022-09-22 01:26:46 +02:00
{
2023-06-18 16:56:33 +02:00
Rectangle bounds = new Rectangle ( new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) , tileSize ) ;
2022-09-22 01:26:46 +02:00
graphics . DrawRectangle ( boundsPen , bounds ) ;
}
}
2022-09-13 10:44:15 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderAllBoundsFromPoint < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( Point , T ) > renderList , Color boundsColor )
2022-09-13 10:44:15 +02:00
{
2023-06-18 16:56:33 +02:00
using ( Pen boundsPen = new Pen ( boundsColor , Math . Max ( 1 , tileSize . Width / 16.0f ) ) )
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
foreach ( ( Point topLeft , T _ ) in renderList . Where ( pt = > visibleCells . Contains ( pt . Item1 ) ) )
2022-09-13 10:44:15 +02:00
{
2023-06-18 16:56:33 +02:00
Rectangle bounds = new Rectangle ( new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) , tileSize ) ;
2022-09-13 10:44:15 +02:00
graphics . DrawRectangle ( boundsPen , bounds ) ;
}
}
}
2023-12-07 21:57:56 +01:00
public static void RenderAllOccupierBoundsGreen < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( Point , T ) > occupiers ) where T : ICellOccupier , ICellOverlapper
{
RenderAllOccupierBounds ( graphics , visibleCells , tileSize , occupiers , Color . Green , Color . Transparent ) ;
}
public static void RenderAllOccupierCellsRed < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( Point , T ) > occupiers ) where T : ICellOccupier , ICellOverlapper
{
RenderAllOccupierBounds ( graphics , visibleCells , tileSize , occupiers , Color . Transparent , Color . Red ) ;
}
2023-06-23 02:35:01 +02:00
public static void RenderAllOccupierBounds < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( Point , T ) > occupiers ) where T : ICellOccupier , ICellOverlapper
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
RenderAllOccupierBounds ( graphics , visibleCells , tileSize , occupiers , Color . Green , Color . Red ) ;
2022-09-13 10:44:15 +02:00
}
2023-12-07 21:57:56 +01:00
public static void RenderAllOccupierBounds < T > ( Graphics graphics , Rectangle visibleCells , Size tileSize , IEnumerable < ( Point , T ) > occupiers , Color boundsColor , Color occupierColor ) where T : ICellOccupier , ICellOverlapper
2022-09-13 10:44:15 +02:00
{
float boundsPenSize = Math . Max ( 1 , tileSize . Width / 16.0f ) ;
float occupyPenSize = Math . Max ( 0.5f , tileSize . Width / 32.0f ) ;
if ( occupyPenSize = = boundsPenSize )
{
boundsPenSize + = 2 ;
}
2023-12-07 21:57:56 +01:00
if ( boundsColor . A ! = 0 )
2022-09-13 10:44:15 +02:00
{
2023-12-07 21:57:56 +01:00
using ( Pen boundsPen = new Pen ( boundsColor , boundsPenSize ) )
2022-09-13 10:44:15 +02:00
{
2023-12-07 21:57:56 +01:00
foreach ( ( Point topLeft , T occupier ) in occupiers )
{
Rectangle typeBounds = occupier . OverlapBounds ;
Rectangle bounds = new Rectangle (
new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ,
new Size ( typeBounds . Width * tileSize . Width , typeBounds . Height * tileSize . Height )
) ;
graphics . DrawRectangle ( boundsPen , bounds ) ;
}
2022-09-13 10:44:15 +02:00
}
2023-12-07 21:57:56 +01:00
}
if ( occupierColor . A ! = 0 )
{
using ( Pen occupyPen = new Pen ( occupierColor , occupyPenSize ) )
2022-09-13 10:44:15 +02:00
{
2023-12-07 21:57:56 +01:00
foreach ( ( Point topLeft , T occupier ) in occupiers )
2022-09-13 10:44:15 +02:00
{
2023-12-07 21:57:56 +01:00
bool [ , ] occupyMask = occupier is Building bl ? bl . Type . BaseOccupyMask : occupier . OccupyMask ;
2024-05-10 00:03:03 +02:00
for ( int y = 0 ; y < occupyMask . GetLength ( 0 ) ; + + y )
2022-09-13 10:44:15 +02:00
{
2024-05-10 00:03:03 +02:00
for ( int x = 0 ; x < occupyMask . GetLength ( 1 ) ; + + x )
2022-09-13 10:44:15 +02:00
{
2023-12-07 21:57:56 +01:00
if ( ! occupyMask [ y , x ] )
{
continue ;
}
2023-06-23 02:35:01 +02:00
Rectangle occupyCellBounds = new Rectangle ( new Point ( topLeft . X + x , topLeft . Y + y ) , new Size ( 1 , 1 ) ) ;
2023-06-18 16:56:33 +02:00
Rectangle occupyBounds = new Rectangle (
2022-09-13 10:44:15 +02:00
new Point ( ( topLeft . X + x ) * tileSize . Width , ( topLeft . Y + y ) * tileSize . Height ) , tileSize ) ;
2023-06-23 02:35:01 +02:00
if ( visibleCells . Contains ( occupyCellBounds ) )
{
graphics . DrawRectangle ( occupyPen , occupyBounds ) ;
}
2022-09-13 10:44:15 +02:00
}
}
}
}
}
}
2024-07-19 15:41:12 +02:00
2023-12-02 01:37:52 +01:00
public static void RenderAllCrateOutlines ( Graphics g , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , double tileScale , bool onlyIfBehindObjects )
2024-07-03 21:54:25 +02:00
{
2024-07-19 15:41:12 +02:00
RenderAllOverlayOutlines ( g , gameInfo , map , visibleCells , tileSize , tileScale , OverlayTypeFlag . WoodCrate , onlyIfBehindObjects , Globals . OutlineColorCrateWood ) ;
RenderAllOverlayOutlines ( g , gameInfo , map , visibleCells , tileSize , tileScale , OverlayTypeFlag . SteelCrate , onlyIfBehindObjects , Globals . OutlineColorCrateSteel ) ;
}
public static void RenderAllSolidOverlayOutlines ( Graphics g , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , double tileScale , bool onlyIfBehindObjects )
{
RenderAllOverlayOutlines ( g , gameInfo , map , visibleCells , tileSize , tileScale , OverlayTypeFlag . Solid , onlyIfBehindObjects , Globals . OutlineColorSolidOverlay ) ;
RenderAllOverlayOutlines ( g , gameInfo , map , visibleCells , tileSize , tileScale , OverlayTypeFlag . Wall , onlyIfBehindObjects , Globals . OutlineColorWall ) ;
2024-07-03 21:54:25 +02:00
}
public static void RenderAllOverlayOutlines ( Graphics g , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , double tileScale , OverlayTypeFlag types ,
bool onlyIfBehindObjects , Color outlineColor )
2023-03-18 01:42:43 +01:00
{
2024-07-19 15:41:12 +02:00
if ( outlineColor . A = = 0 )
{
return ;
}
2024-07-04 15:45:50 +02:00
Dictionary < Point , Overlay > includedPoints = new Dictionary < Point , Overlay > ( ) ;
List < int > includedCells = new List < int > ( ) ;
2023-03-18 01:42:43 +01:00
float outlineThickness = 0.05f ;
2023-06-21 12:18:49 +02:00
byte alphaThreshold = ( byte ) ( Globals . UseClassicFiles ? 0x80 : 0x40 ) ;
2024-07-04 15:45:50 +02:00
// Get all included points in an initial sweep so they're all available in the second processing step.
2024-05-10 00:03:03 +02:00
foreach ( ( int cell , Overlay overlay ) in map . Overlay )
2023-03-18 01:42:43 +01:00
{
OverlayType ovlt = overlay . Type ;
2024-09-09 13:06:06 +02:00
if ( ! ovlt . Flag . HasAnyFlags ( types ) | | ! map . Metrics . GetLocation ( cell , out Point location ) | | ! visibleCells . Contains ( location ) )
2023-03-19 00:06:21 +01:00
{
continue ;
}
2024-09-08 19:44:13 +02:00
if ( onlyIfBehindObjects )
{
if ( overlay . Type . OpaqueMask = = null ) {
continue ;
}
bool [ ] toCheck = overlay . Type . OpaqueMask [ 0 , 0 ] ;
InfantryStoppingType [ ] toLoop = Enum . GetValues ( typeof ( InfantryStoppingType ) ) . Cast < InfantryStoppingType > ( ) . ToArray ( ) ;
bool occupied = false ;
for ( int i = 0 ; i < toLoop . Length ; i + + )
{
if ( ! toCheck [ i ] )
{
continue ;
}
InfantryStoppingType ist = toLoop [ i ] ;
2024-09-09 12:39:00 +02:00
if ( IsOverlapped ( map , location , true , ist , overlay , - 1 ) )
2024-09-08 19:44:13 +02:00
{
occupied = true ;
break ;
}
}
if ( ! occupied )
{
continue ;
}
}
2024-07-04 15:45:50 +02:00
includedCells . Add ( cell ) ;
includedPoints . Add ( location , overlay ) ;
2024-07-03 21:54:25 +02:00
}
2024-07-04 15:45:50 +02:00
// Now we have all cells to process, we can ensure the outlines for neighbouring wall cells are combined.
foreach ( int cell in includedCells )
2024-07-03 21:54:25 +02:00
{
2024-07-04 15:45:50 +02:00
if ( ! map . Overlay . Metrics . GetLocation ( cell , out Point p ) | | ! includedPoints . TryGetValue ( p , out Overlay overlay ) )
2024-07-03 21:54:25 +02:00
{
continue ;
}
2024-07-04 15:45:50 +02:00
OverlayType ovlt = overlay . Type ;
2024-07-03 21:54:25 +02:00
Size cellSize = new Size ( 1 , 1 ) ;
Color outlineCol = Color . FromArgb ( 0xA0 , outlineColor ) ;
2024-07-04 15:45:50 +02:00
Overlay tstOvl ;
2024-07-19 15:41:12 +02:00
// If this is a wall, exclude edge cells on sides that are the same type of wall, so they connect properly.
2024-07-04 15:45:50 +02:00
bool includeAbove = ! ovlt . IsWall | | ! includedPoints . TryGetValue ( p . OffsetPoint ( 0 , - 1 ) , out tstOvl ) | | tstOvl . Type . ID ! = ovlt . ID ;
bool includeRight = ! ovlt . IsWall | | ! includedPoints . TryGetValue ( p . OffsetPoint ( 1 , 0 ) , out tstOvl ) | | tstOvl . Type . ID ! = ovlt . ID ;
bool includeBelow = ! ovlt . IsWall | | ! includedPoints . TryGetValue ( p . OffsetPoint ( 0 , 1 ) , out tstOvl ) | | tstOvl . Type . ID ! = ovlt . ID ;
bool includeLefty = ! ovlt . IsWall | | ! includedPoints . TryGetValue ( p . OffsetPoint ( - 1 , 0 ) , out tstOvl ) | | tstOvl . Type . ID ! = ovlt . ID ;
2024-07-03 21:54:25 +02:00
int aroundMask = ( includeAbove ? 1 : 0 ) | ( includeRight ? 2 : 0 ) | ( includeBelow ? 4 : 0 ) | ( includeLefty ? 8 : 0 ) ;
2024-07-19 15:41:12 +02:00
string ovlId = "outline_ovl_" + overlay . Type . Name + "_" + overlay . Icon + "_" + aroundMask + "_" + tileSize . Width + "x" + tileSize . Height ;
RegionData paintAreaRel = Globals . TheShapeCacheManager . GetShape ( ovlId ) ;
if ( paintAreaRel = = null )
2023-03-19 00:06:21 +01:00
{
2024-07-03 21:54:25 +02:00
using ( Bitmap bm = new Bitmap ( tileSize . Width * 3 , tileSize . Height * 3 , PixelFormat . Format32bppArgb ) )
2023-06-21 12:18:49 +02:00
{
using ( Graphics ig = Graphics . FromImage ( bm ) )
{
2024-07-29 10:08:56 +02:00
RenderOverlay ( gameInfo , new Point ( 1 , 1 ) , null , tileSize , tileScale , overlay , true ) . Item2 ( ig ) ;
2023-06-21 12:18:49 +02:00
}
2023-07-05 21:49:17 +02:00
paintAreaRel = ImageUtils . GetOutline ( tileSize , bm , outlineThickness , alphaThreshold , Globals . UseClassicFiles ) ;
2024-07-19 15:41:12 +02:00
// Wall connecting: if any side cells should be excluded so they connect to neighbouring cells,
2024-07-03 21:54:25 +02:00
// intersect the region with another region containing only the desired side cells.
if ( ovlt . IsWall & & aroundMask ! = ( 1 | 2 | 4 | 8 ) )
{
using ( Region outline = new Region ( paintAreaRel ) )
{
using ( Region rgn = new Region ( new Rectangle ( new Point ( tileSize . Width , tileSize . Height ) , tileSize ) ) )
{
// Adds side cells that aren't cut off.
if ( includeAbove ) rgn . Union ( new Rectangle ( new Point ( tileSize . Width /**/ , /**/ 0 ) , tileSize ) ) ;
if ( includeRight ) rgn . Union ( new Rectangle ( new Point ( tileSize . Width * 2 , tileSize . Height /**/ ) , tileSize ) ) ;
if ( includeBelow ) rgn . Union ( new Rectangle ( new Point ( tileSize . Width /**/ , tileSize . Height * 2 ) , tileSize ) ) ;
if ( includeLefty ) rgn . Union ( new Rectangle ( new Point ( /**/ 0 , tileSize . Height /**/ ) , tileSize ) ) ;
// Not sure if needed; adds corner cells as well if neighbouring cells are added.
if ( includeAbove & & includeRight ) rgn . Union ( new Rectangle ( new Point ( tileSize . Width * 2 , /**/ 0 ) , tileSize ) ) ;
if ( includeRight & & includeBelow ) rgn . Union ( new Rectangle ( new Point ( tileSize . Width * 2 , tileSize . Height * 2 ) , tileSize ) ) ;
if ( includeBelow & & includeLefty ) rgn . Union ( new Rectangle ( new Point ( /**/ 0 , tileSize . Height * 2 ) , tileSize ) ) ;
if ( includeLefty & & includeAbove ) rgn . Union ( new Rectangle ( new Point ( /**/ 0 , /**/ 0 ) , tileSize ) ) ;
outline . Intersect ( rgn ) ;
paintAreaRel = outline . GetRegionData ( ) ;
}
}
}
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddShape ( ovlId , paintAreaRel ) ;
2023-06-21 12:18:49 +02:00
}
2023-03-19 00:06:21 +01:00
}
2024-07-03 21:54:25 +02:00
int actualTopLeftX = tileSize . Width * ( p . X - 1 ) ;
int actualTopLeftY = tileSize . Height * ( p . Y - 1 ) ;
2023-03-19 00:06:21 +01:00
if ( paintAreaRel ! = null )
{
using ( Region paintArea = new Region ( paintAreaRel ) )
2023-03-18 01:42:43 +01:00
using ( Brush brush = new SolidBrush ( outlineCol ) )
{
2023-03-19 00:06:21 +01:00
paintArea . Translate ( actualTopLeftX , actualTopLeftY ) ;
2023-03-18 01:42:43 +01:00
g . FillRegion ( brush , paintArea ) ;
}
}
}
}
2023-06-23 02:35:01 +02:00
public static void RenderAllInfantryOutlines ( Graphics g , Map map , Rectangle visibleCells , Size tileSize , bool onlyIfBehindObjects )
2023-06-21 12:18:49 +02:00
{
float outlineThickness = 0.05f ;
byte alphaThreshold = ( byte ) ( Globals . UseClassicFiles ? 0x80 : 0x40 ) ;
//double lumThreshold = 0.01d;
2023-06-23 02:35:01 +02:00
visibleCells . Inflate ( 1 , 1 ) ;
2023-06-21 12:18:49 +02:00
foreach ( var ( location , infantryGroup ) in map . Technos . OfType < InfantryGroup > ( ) . OrderBy ( i = > map . Metrics . GetCell ( i . Location ) ) )
{
Size cellSize = new Size ( 1 , 1 ) ;
2023-06-23 02:35:01 +02:00
if ( ! visibleCells . Contains ( location ) )
2023-06-21 12:18:49 +02:00
{
continue ;
}
2024-09-08 19:44:13 +02:00
InfantryStoppingType [ ] toCheck = Enum . GetValues ( typeof ( InfantryStoppingType ) ) . Cast < InfantryStoppingType > ( ) . ToArray ( ) ;
foreach ( InfantryStoppingType ist in toCheck )
2023-06-21 12:18:49 +02:00
{
2023-07-21 13:15:41 +02:00
Infantry infantry = infantryGroup . Infantry [ ( int ) ist ] ;
2023-06-21 12:18:49 +02:00
if ( infantry = = null )
{
continue ;
}
2023-07-21 13:15:41 +02:00
if ( onlyIfBehindObjects )
{
2024-09-08 19:44:13 +02:00
if ( ! IsOverlapped ( map , location , false , ist , infantryGroup , infantry . DrawOrderCache ) )
2023-07-21 13:15:41 +02:00
{
continue ;
}
}
2024-07-15 00:15:10 +02:00
Color outlineCol = Color . FromArgb ( 0x80 , Globals . TheTeamColorManager . GetBaseColor ( infantry . House ? . UnitTeamColor ) ) ;
2024-07-19 15:41:12 +02:00
string infId = "outline_inf_" + infantry . Type . Name + '_' + ( ( int ) ist ) + '_' + infantry . Direction . ID + "_" + tileSize . Width + "x" + tileSize . Height ;
RegionData paintAreaRel = Globals . TheShapeCacheManager . GetShape ( infId ) ;
if ( paintAreaRel = = null )
2023-06-21 12:18:49 +02:00
{
using ( Bitmap bm = new Bitmap ( tileSize . Width * 3 , tileSize . Height * 3 , PixelFormat . Format32bppArgb ) )
{
using ( Graphics ig = Graphics . FromImage ( bm ) )
{
2024-07-29 10:08:56 +02:00
RenderInfantry ( new Point ( 1 , 1 ) , tileSize , infantry , ist , true ) . RenderAction ( ig ) ;
2023-06-21 12:18:49 +02:00
}
2023-07-05 21:49:17 +02:00
paintAreaRel = ImageUtils . GetOutline ( tileSize , bm , outlineThickness , alphaThreshold , Globals . UseClassicFiles ) ;
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddShape ( infId , paintAreaRel ) ;
2023-06-21 12:18:49 +02:00
}
}
// Rendered in a 3x3 cell frame, so subtract one.
int actualTopLeftX = ( location . X - 1 ) * tileSize . Width ;
int actualTopLeftY = ( location . Y - 1 ) * tileSize . Height ;
if ( paintAreaRel ! = null )
{
using ( Region paintArea = new Region ( paintAreaRel ) )
using ( Brush brush = new SolidBrush ( outlineCol ) )
{
paintArea . Translate ( actualTopLeftX , actualTopLeftY ) ;
g . FillRegion ( brush , paintArea ) ;
}
}
}
}
}
2024-07-03 21:54:25 +02:00
public static void RenderAllUnitOutlines ( Graphics g , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , bool onlyIfBehindObjects )
2023-06-21 12:18:49 +02:00
{
2024-07-03 21:54:25 +02:00
RenderAllObjectOutlines ( g , gameInfo , map , map . Technos . OfType < Unit > ( ) , visibleCells , tileSize , true ,
2024-07-29 10:08:56 +02:00
( h ) = > h . UnitTeamColor , ( gr , p , unt ) = > RenderUnit ( gameInfo , new Point ( 1 , 1 ) , tileSize , unt , true ) . RenderAction ( gr ) , Color . Black ) ;
2023-06-21 12:18:49 +02:00
}
2024-05-11 13:20:27 +02:00
public static void RenderAllBuildingOutlines ( Graphics g , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , double tileScale , bool onlyIfBehindObjects )
2024-07-03 21:54:25 +02:00
{
RenderAllObjectOutlines ( g , gameInfo , map , map . Buildings . OfType < Building > ( ) , visibleCells , tileSize , true ,
2024-07-29 10:08:56 +02:00
( h ) = > h . BuildingTeamColor , ( gr , p , bld ) = > RenderBuilding ( gameInfo , null , p , tileSize , tileScale , bld , true ) . RenderAction ( gr ) , Color . Black ) ;
2024-07-03 21:54:25 +02:00
}
public static void RenderAllTerrainOutlines ( Graphics g , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , double tileScale , bool onlyIfBehindObjects )
{
RenderAllObjectOutlines ( g , gameInfo , map , map . Technos . OfType < Terrain > ( ) , visibleCells , tileSize , true ,
2024-07-29 10:08:56 +02:00
null , ( gr , p , trn ) = > RenderTerrain ( p , tileSize , tileScale , trn , true ) . RenderAction ( gr ) , Globals . OutlineColorTerrain ) ;
2024-07-03 21:54:25 +02:00
}
/// <summary>
/// Unified function for rendering outlines for techno objects. Infantry are still separate, because of the whole InfantryGroup mess.
/// </summary>
/// <typeparam name="T">Techno that's being handled.</typeparam>
/// <param name="g">Graphics object to paint on.</param>
/// <param name="gameInfo">GameInfo object for this game.</param>
/// <param name="map">Map, to check metrics and object overlaps.</param>
/// <param name="occupiers">The list of occupiers to render outlines for.</param>
/// <param name="visibleCells">Visible area on the screen in which these outlines should be rendered.</param>
/// <param name="tileSize">Size of the map tiles.</param>
/// <param name="onlyIfBehindObjects">True to only render objects that are overlapped by something.</param>
/// <param name="colorPick">Function to get the preferred team color name from a House.</param>
/// <param name="RenderAction">Action to render an object of the handled type at a specific point on a graphics object.</param>
/// <param name="fallbackColor">Fallback colour in case no House or <paramref name="colorPick"/> function are available.</param>
private static void RenderAllObjectOutlines < T > ( Graphics g , GameInfo gameInfo , Map map , IEnumerable < ( Point Location , T Occupier ) > occupiers ,
2024-07-29 10:08:56 +02:00
Rectangle visibleCells , Size tileSize , bool onlyIfBehindObjects , Func < HouseType , string > colorPick , Action < Graphics , Point , T > RenderAction , Color fallbackColor )
2024-07-03 21:54:25 +02:00
where T : ITechno , ICellOverlapper , ICellOccupier , ICloneable
2024-05-11 13:20:27 +02:00
{
float outlineThickness = 0.05f ;
byte alphaThreshold = ( byte ) ( Globals . UseClassicFiles ? 0x80 : 0x40 ) ;
//double lumThreshold = 0.01d;
visibleCells . Inflate ( 1 , 1 ) ;
2024-07-03 21:54:25 +02:00
foreach ( ( Point objLocation , T placedObj ) in occupiers . OrderBy ( i = > map . Metrics . GetCell ( i . Location ) ) )
2024-05-11 13:20:27 +02:00
{
2024-07-03 21:54:25 +02:00
// This is a visibility check; check cells that are deemed "visible".
2024-09-07 16:47:32 +02:00
bool [ , ] [ ] opaqueMask = placedObj . OpaqueMask ;
2024-07-03 21:54:25 +02:00
bool [ , ] occupyMask = placedObj . OccupyMask ;
2024-07-29 18:38:08 +02:00
int paintOrder = placedObj . DrawOrderCache ;
2024-07-03 21:54:25 +02:00
int maskY = opaqueMask = = null ? 0 : opaqueMask . GetLength ( 0 ) ;
int maskX = opaqueMask = = null ? 0 : opaqueMask . GetLength ( 1 ) ;
2024-05-11 13:20:27 +02:00
// If not in currently viewed area, ignore.
2024-07-03 21:54:25 +02:00
Rectangle objBounds = new Rectangle ( objLocation , placedObj . OccupyMask . GetDimensions ( ) ) ;
if ( ! visibleCells . IntersectsWith ( objBounds ) )
2024-05-11 13:20:27 +02:00
{
continue ;
}
2024-05-12 17:49:52 +02:00
if ( onlyIfBehindObjects )
2024-05-11 13:20:27 +02:00
{
2024-07-03 21:54:25 +02:00
// Select actual map points for all visible points in opaqueMask
2024-07-29 18:38:08 +02:00
bool allOpaque = true ;
2024-09-07 16:47:32 +02:00
// only evaluate center point
2024-07-03 21:54:25 +02:00
Point [ ] opaquePoints = Enumerable . Range ( 0 , maskY ) . SelectMany ( nrY = > Enumerable . Range ( 0 , maskX ) . Select ( nrX = > new Point ( nrX , nrY ) ) )
2024-09-08 19:44:13 +02:00
. Where ( pt = > opaqueMask [ pt . Y , pt . X ] ! = null & & opaqueMask [ pt . Y , pt . X ] . Length > 0 & & opaqueMask [ pt . Y , pt . X ] . Any ( ) ) . ToArray ( ) ;
2024-07-03 21:54:25 +02:00
foreach ( Point opaquePoint in opaquePoints )
2024-05-11 13:20:27 +02:00
{
2024-09-08 19:44:13 +02:00
Point realPoint = new Point ( objLocation . X + opaquePoint . X , objLocation . Y + opaquePoint . Y ) ;
InfantryStoppingType [ ] toCheck = Enum . GetValues ( typeof ( InfantryStoppingType ) ) . Cast < InfantryStoppingType > ( ) . ToArray ( ) ;
bool isSubOverlapped = false ;
bool [ ] opaqueCellMask = opaqueMask [ opaquePoint . Y , opaquePoint . X ] ;
foreach ( InfantryStoppingType ist in toCheck )
{
if ( ! opaqueCellMask [ ( int ) ist ] )
{
continue ;
}
if ( IsOverlapped ( map , realPoint , false , ist , placedObj , paintOrder ) )
{
isSubOverlapped = true ;
break ;
}
}
// if any of the occupied sub-cells are overlapped, consider it partially overlapped and thus eligible for outline.
if ( ! isSubOverlapped )
2024-05-12 17:49:52 +02:00
{
2024-07-29 18:38:08 +02:00
allOpaque = false ;
break ;
2024-05-12 17:49:52 +02:00
}
}
2024-07-29 18:38:08 +02:00
if ( ! allOpaque )
2024-05-12 17:49:52 +02:00
{
continue ;
2024-05-11 13:20:27 +02:00
}
}
Size cellSize = new Size ( maskX + 2 , maskY + 2 ) ;
2024-07-03 21:54:25 +02:00
Color houseCol = fallbackColor ;
if ( placedObj . House ! = null & & colorPick ! = null )
{
2024-07-15 00:15:10 +02:00
houseCol = Color . FromArgb ( 0x80 , Globals . TheTeamColorManager . GetBaseColor ( colorPick ( placedObj . House ) ) ) ;
2024-07-03 21:54:25 +02:00
}
2024-08-13 15:34:23 +02:00
string id = "outline_" + typeof ( T ) . Name + "_" + placedObj . TechnoType . Name + "_fr" + placedObj . DrawFrameCache + "_" + tileSize . Width + "x" + tileSize . Height ;
2024-07-19 15:41:12 +02:00
RegionData paintAreaRel = Globals . TheShapeCacheManager . GetShape ( id ) ;
if ( paintAreaRel = = null )
2024-05-11 13:20:27 +02:00
{
2024-07-29 10:08:56 +02:00
// Clone without preview flag.
2024-07-03 21:54:25 +02:00
T toRender = placedObj ;
2024-07-29 10:08:56 +02:00
if ( placedObj . IsPreview )
2024-05-11 13:20:27 +02:00
{
2024-07-03 21:54:25 +02:00
toRender = ( T ) placedObj . Clone ( ) ;
2024-07-29 10:08:56 +02:00
placedObj . IsPreview = false ;
if ( placedObj is Building bld )
{
bld . BasePriority = 0 ;
bld . IsPrebuilt = true ;
}
2024-05-11 13:20:27 +02:00
}
using ( Bitmap bm = new Bitmap ( tileSize . Width * cellSize . Width , tileSize . Height * cellSize . Width , PixelFormat . Format32bppArgb ) )
{
using ( Graphics ig = Graphics . FromImage ( bm ) )
{
2024-07-03 21:54:25 +02:00
RenderAction ( ig , new Point ( 1 , 1 ) , toRender ) ;
2024-05-11 13:20:27 +02:00
}
paintAreaRel = ImageUtils . GetOutline ( tileSize , bm , outlineThickness , alphaThreshold , Globals . UseClassicFiles ) ;
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddShape ( id , paintAreaRel ) ;
2024-05-11 13:20:27 +02:00
}
}
2024-07-03 21:54:25 +02:00
int paintPosTopLeftX = ( objLocation . X - 1 ) * tileSize . Width ;
int paintPosTopLeftY = ( objLocation . Y - 1 ) * tileSize . Height ;
2024-05-11 13:20:27 +02:00
if ( paintAreaRel ! = null )
{
using ( Region paintArea = new Region ( paintAreaRel ) )
2024-07-10 13:36:49 +02:00
using ( Brush brush = new SolidBrush ( houseCol ) )
2024-05-11 13:20:27 +02:00
{
2024-07-03 21:54:25 +02:00
paintArea . Translate ( paintPosTopLeftX , paintPosTopLeftY ) ;
2024-05-11 13:20:27 +02:00
g . FillRegion ( brush , paintArea ) ;
}
}
}
}
2023-07-20 01:07:00 +02:00
/// <summary>
/// Check if an object is considered overlapped by something on the map.
/// </summary>
2024-07-29 18:38:08 +02:00
/// <param name="map">Map to check on.</param>
/// <param name="location">Location to check for overlap.</param>
/// <param name="unitsAlwaysOverlap">True to immediately return true if the cell is occupied by units.</param>
2023-07-21 13:15:41 +02:00
/// <param name="ist">When filled in, the overlapper is treated as infantry on that location.</param>
2024-05-12 17:49:52 +02:00
/// <param name="objectToCheck">Object for which overlap is being checked. This object is automatically ignored in the objects it loops over to check for overlaps.</param>
2024-09-08 19:44:13 +02:00
/// <param name="objectPaintOrder">Cached paint order of the object, to easily check if it can be overlapped at all.</param>
2023-07-20 01:07:00 +02:00
/// <returns>true if the cell is considered filled enough to overlap things.</returns>
2024-09-08 19:44:13 +02:00
private static bool IsOverlapped ( Map map , Point location , bool unitsAlwaysOverlap , InfantryStoppingType ? ist , ICellOverlapper objectToCheck , int objectPaintOrder )
2023-03-19 00:06:21 +01:00
{
ICellOccupier techno = map . Technos [ location ] ;
2024-09-08 19:44:13 +02:00
int subIndex = ist . HasValue ? ( int ) ist . Value : 0 ;
2024-08-09 12:45:08 +02:00
// Single-cell occupier. Always pass. Since this is only used for single-cell objects, only map.Technos needs to be checked.
2024-07-29 18:38:08 +02:00
if ( unitsAlwaysOverlap & & ( techno is Unit | | techno is InfantryGroup ) & & techno ! = objectToCheck )
2023-03-19 00:06:21 +01:00
{
2024-09-08 19:44:13 +02:00
if ( ! ( techno is InfantryGroup ig ) | | ig . Infantry [ subIndex ] ! = null )
{
return true ;
}
2023-03-19 00:06:21 +01:00
}
// Logic for multi-cell occupiers; buildings and terrain.
// Return true if either an occupied cell, or overlayed by graphics deemed opaque.
2024-07-29 18:38:08 +02:00
ICellOverlapper [ ] technos = map . Overlappers . OverlappersAt ( location ) . Where ( ov = > ! ReferenceEquals ( ov , objectToCheck ) ) . ToArray ( ) ;
if ( technos . Length = = 0 )
2023-03-19 00:06:21 +01:00
{
return false ;
}
2024-08-09 12:45:08 +02:00
// Overlappers list contains both buildings and other technos.
2023-03-19 00:06:21 +01:00
foreach ( ICellOverlapper ovl in technos )
{
ICellOccupier occ = ovl as ICellOccupier ;
2024-07-10 13:36:49 +02:00
if ( occ = = null )
{
continue ;
}
2024-09-07 16:47:32 +02:00
bool [ , ] [ ] opaqueMask = ovl . OpaqueMask ;
2024-05-11 13:20:27 +02:00
int maskY = opaqueMask = = null ? 0 : opaqueMask . GetLength ( 0 ) ;
2023-07-20 01:07:00 +02:00
int maskX = opaqueMask = = null ? 0 : opaqueMask . GetLength ( 1 ) ;
2024-07-29 18:38:08 +02:00
Point ? pt = map . Technos [ occ ] ? ? map . Buildings [ occ ] ;
2023-03-19 00:06:21 +01:00
if ( ! pt . HasValue )
{
continue ;
}
2024-07-29 18:38:08 +02:00
// Object we're comparing with was drawn before the current one, so it can't possible overlap it.
// This caching allows extremely easy checks on overlap without much processing.
if ( ovl is ITechno paintedObj & & paintedObj . DrawOrderCache < objectPaintOrder )
2023-07-21 13:15:41 +02:00
{
2024-07-29 18:38:08 +02:00
continue ;
2023-07-21 13:15:41 +02:00
}
2024-09-08 19:44:13 +02:00
if ( ovl is InfantryGroup ig & & ( ig . Infantry [ subIndex ] = = null | | ig . Infantry [ subIndex ] . DrawOrderCache < objectPaintOrder ) )
{
continue ;
}
2023-03-19 00:06:21 +01:00
// Get list of points, find current point in the list.
Rectangle boundsRect = new Rectangle ( pt . Value , new Size ( maskX , maskY ) ) ;
2023-07-09 00:55:19 +02:00
List < Point > pts = boundsRect . Points ( ) . OrderBy ( p = > p . Y * map . Metrics . Width + p . X ) . ToList ( ) ;
2023-03-19 00:06:21 +01:00
int index = pts . IndexOf ( location ) ;
if ( index = = - 1 )
{
continue ;
}
// Trick to convert 2-dimensional arrays to linear format.
2024-09-07 16:47:32 +02:00
bool [ ] [ ] opaqueArr = opaqueMask . Cast < bool [ ] > ( ) . ToArray ( ) ;
2024-09-08 19:44:13 +02:00
if ( index < opaqueArr . Length & & opaqueArr [ index ] ! = null & & subIndex < opaqueArr [ index ] . Length & & opaqueArr [ index ] [ subIndex ] )
2023-03-19 00:06:21 +01:00
{
2024-07-29 18:38:08 +02:00
// If obscured from view by graphics, return true.
2023-03-19 00:06:21 +01:00
return true ;
}
}
return false ;
}
2023-12-02 01:37:52 +01:00
public static void RenderAllFootballAreas ( Graphics graphics , Map map , Rectangle visibleCells , Size tileSize , double tileScale , GameInfo gameInfo )
2022-09-25 12:11:59 +02:00
{
2023-12-02 01:37:52 +01:00
if ( ! gameInfo . SupportsMapLayer ( MapLayerFlag . FootballArea ) )
2022-09-25 12:11:59 +02:00
{
return ;
}
HashSet < Point > footballPoints = new HashSet < Point > ( ) ;
2023-06-23 02:35:01 +02:00
Rectangle renderArea = map . Metrics . Bounds ;
renderArea . Intersect ( visibleCells ) ;
2022-09-25 12:11:59 +02:00
foreach ( Waypoint waypoint in map . Waypoints )
{
if ( ! waypoint . Point . HasValue | | Waypoint . GetMpIdFromFlag ( waypoint . Flag ) = = - 1 )
{
continue ;
}
Point [ ] roadPoints = new Rectangle ( waypoint . Point . Value . X - 1 , waypoint . Point . Value . Y - 1 , 4 , 3 ) . Points ( ) . ToArray ( ) ;
2023-06-23 02:35:01 +02:00
foreach ( Point p in roadPoints . Where ( p = > renderArea . Contains ( p ) ) )
2022-09-25 12:11:59 +02:00
{
footballPoints . Add ( p ) ;
}
}
foreach ( Point p in footballPoints . OrderBy ( p = > p . Y * map . Metrics . Width + p . X ) )
{
Overlay footballTerrain = new Overlay ( )
{
Type = SoleSurvivor . OverlayTypes . Road ,
2024-07-29 10:08:56 +02:00
IsPreview = true ,
2022-09-25 12:11:59 +02:00
} ;
2024-07-29 10:08:56 +02:00
RenderOverlay ( gameInfo , p , null , tileSize , tileScale , footballTerrain , false ) . Item2 ( graphics ) ;
2022-09-25 12:11:59 +02:00
}
}
2024-08-14 12:53:56 +02:00
public static void RenderWaypointFlags ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , ShapeCacheManager cacheManager )
2022-09-25 12:11:59 +02:00
{
// Re-render flags on top of football areas.
2024-07-29 10:08:56 +02:00
List < Waypoint > flagWayPoints = new List < Waypoint > ( ) ;
Dictionary < int , int > flagOverlapMpCheck = new Dictionary < int , int > ( ) ;
2024-07-09 12:26:25 +02:00
Dictionary < Waypoint , int > flagOffsets = new Dictionary < Waypoint , int > ( ) ;
2024-07-29 10:08:56 +02:00
// Get all waypoints. Ignore the preview if it is on the same cell as the same actual waypoint.
// Preview waypoint is always only one, and added at the end, so a sequential run always works.
2022-09-25 12:11:59 +02:00
foreach ( Waypoint waypoint in map . Waypoints )
{
2024-07-29 10:08:56 +02:00
int mpId = Waypoint . GetMpIdFromFlag ( waypoint . Flag ) ;
if ( waypoint . Point . HasValue & & mpId > = 0 & & visibleCells . Contains ( waypoint . Point . Value )
2024-07-09 12:26:25 +02:00
& & map . Metrics . GetCell ( waypoint . Point . Value , out int cell ) )
2022-09-25 12:11:59 +02:00
{
2024-07-29 10:08:56 +02:00
bool alreadyExists = flagOverlapMpCheck . TryGetValue ( mpId , out int mpCell ) & & cell = = mpCell ;
if ( ! alreadyExists )
{
flagWayPoints . Add ( waypoint ) ;
flagOverlapMpCheck [ mpId ] = cell ;
}
}
}
// Create offsets if multiple flags are on the same cell.
flagWayPoints = flagWayPoints . OrderBy ( w = > Waypoint . GetMpIdFromFlag ( w . Flag ) ) . ToList ( ) ;
Dictionary < int , int > flagOverlapPoints = new Dictionary < int , int > ( ) ;
foreach ( Waypoint waypoint in flagWayPoints )
{
if ( map . Metrics . GetCell ( waypoint . Point . Value , out int cell ) )
{
2024-07-09 12:26:25 +02:00
if ( ! flagOverlapPoints . TryGetValue ( cell , out int amount ) )
{
flagOverlapPoints . Add ( cell , 1 ) ;
}
else
{
flagOverlapPoints [ cell ] = amount + 1 ;
}
flagOffsets [ waypoint ] = amount * 2 ;
2022-09-25 12:11:59 +02:00
}
}
2024-07-29 10:08:56 +02:00
// Paint the flags.
2023-06-19 15:28:04 +02:00
ITeamColor [ ] flagColors = map . FlagColors ;
2024-07-29 10:08:56 +02:00
foreach ( Waypoint wp in flagWayPoints )
2022-09-25 12:11:59 +02:00
{
2024-07-09 12:26:25 +02:00
flagOffsets . TryGetValue ( wp , out int offset ) ;
2024-08-14 12:53:56 +02:00
RenderWaypoint ( gameInfo , false , tileSize , flagColors , wp , wp . IsPreview ? Globals . PreviewAlphaFloat : 1.0f , offset , cacheManager ) . Item2 ( graphics ) ;
2022-09-25 12:11:59 +02:00
}
}
2024-07-15 17:39:36 +02:00
public static void RenderAllFakeBuildingLabels ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize )
2022-09-13 10:44:15 +02:00
{
2024-07-15 17:39:36 +02:00
RenderAllFakeBuildingLabels ( graphics , gameInfo , map . Buildings . OfType < Building > ( ) , visibleCells , tileSize ) ;
2022-09-13 10:44:15 +02:00
}
2024-07-15 17:39:36 +02:00
public static void RenderAllFakeBuildingLabels ( Graphics graphics , GameInfo gameInfo , IEnumerable < ( Point topLeft , Building building ) > buildings , Rectangle visibleCells , Size tileSize )
2022-09-13 10:44:15 +02:00
{
2024-07-15 17:39:36 +02:00
Color textColor = Color . White ;
Color backPaintColor = Color . Black ;
2023-06-18 16:56:33 +02:00
StringFormat stringFormat = new StringFormat
2022-09-13 10:44:15 +02:00
{
Alignment = StringAlignment . Center ,
LineAlignment = StringAlignment . Center
} ;
2024-07-15 17:39:36 +02:00
string classicFont = null ;
bool cropClassicFont = false ;
TeamRemap remapClassicFont = null ;
if ( Globals . TheTilesetManager is TilesetManagerClassic tsmc & & Globals . TheTeamColorManager is TeamRemapManager trm )
{
classicFont = gameInfo . GetClassicFontInfo ( ClassicFont . CellTriggers , tsmc , trm , textColor , out cropClassicFont , out remapClassicFont ) ;
}
2023-03-12 02:40:33 +01:00
string fakeText = Globals . TheGameTextManager [ "TEXT_UI_FAKE" ] ;
2023-06-16 21:46:44 +02:00
double tileScaleHor = tileSize . Width / 128.0 ;
2024-07-15 17:39:36 +02:00
using ( SolidBrush fakeBackgroundBrushPrev = new SolidBrush ( Color . FromArgb ( 128 * 2 / 3 , backPaintColor ) ) )
using ( SolidBrush fakeBackgroundBrush = new SolidBrush ( Color . FromArgb ( 256 * 2 / 3 , backPaintColor ) ) )
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2022-09-13 10:44:15 +02:00
{
2024-07-15 17:39:36 +02:00
foreach ( ( Point topLeft , Building building ) in buildings )
2022-09-13 10:44:15 +02:00
{
2024-07-15 17:39:36 +02:00
if ( ! building . Type . IsFake )
{
continue ;
}
Rectangle buildingCellBounds = new Rectangle ( topLeft , building . Type . Size ) ;
if ( ! visibleCells . IntersectsWith ( buildingCellBounds ) )
{
continue ;
}
bool forPreview = building . IsPreview ;
Size maxSize = building . Type . Size ;
Rectangle buildingBounds = new Rectangle (
new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ,
new Size ( maxSize . Width * tileSize . Width , maxSize . Height * tileSize . Height )
) ;
if ( classicFont = = null )
{
2024-07-29 10:08:56 +02:00
using ( SolidBrush fakeTextBrush = new SolidBrush ( Color . FromArgb ( forPreview ? Globals . PreviewAlphaInt : 255 , textColor ) ) )
2024-07-15 17:39:36 +02:00
{
using ( Font font = graphics . GetAdjustedFont ( fakeText , SystemFonts . DefaultFont , buildingBounds . Width , buildingBounds . Height ,
Math . Max ( 1 , ( int ) Math . Round ( 12 * tileScaleHor ) ) , Math . Max ( 1 , ( int ) Math . Round ( 24 * tileScaleHor ) ) , stringFormat , true ) )
{
SizeF textBounds = graphics . MeasureString ( fakeText , font , buildingBounds . Width , stringFormat ) ;
RectangleF backgroundBounds = new RectangleF ( buildingBounds . Location , textBounds ) ;
graphics . FillRectangle ( forPreview ? fakeBackgroundBrushPrev : fakeBackgroundBrush , backgroundBounds ) ;
graphics . DrawString ( fakeText , font , fakeTextBrush , backgroundBounds , stringFormat ) ;
}
}
}
else
{
Rectangle buildingRenderBounds = new Rectangle (
new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ,
new Size ( maxSize . Width * tileSize . Width , maxSize . Height * tileSize . Height )
) ;
2024-07-19 15:41:12 +02:00
string fkId = "fake_classic_" + maxSize . Width + "x" + maxSize . Height ;
Bitmap fkBm = Globals . TheShapeCacheManager . GetImage ( fkId ) ;
if ( fkBm = = null )
2024-07-15 17:39:36 +02:00
{
2024-07-19 15:41:12 +02:00
Rectangle buildingBoundsClassic = new Rectangle ( Point . Empty , new Size ( maxSize . Width * Globals . OriginalTileWidth , maxSize . Height * Globals . OriginalTileHeight ) ) ;
fkBm = new Bitmap ( buildingBoundsClassic . Width , buildingBoundsClassic . Height ) ;
using ( Graphics bmgr = Graphics . FromImage ( fkBm ) )
2024-07-15 17:39:36 +02:00
{
int [ ] indices = Encoding . ASCII . GetBytes ( fakeText ) . Select ( x = > ( int ) x ) . ToArray ( ) ;
using ( Bitmap txt = RenderTextFromSprite ( classicFont , remapClassicFont , Size . Empty , indices , false , cropClassicFont ) )
{
int frameWidth = Math . Min ( txt . Width + 2 , buildingBoundsClassic . Width ) ;
int frameHeight = Math . Min ( txt . Height + 2 , buildingBoundsClassic . Height ) ;
Rectangle backgroundBounds = new Rectangle ( Point . Empty , new Size ( frameWidth , frameHeight ) ) ;
Rectangle frameRect = new Rectangle ( 0 , 0 , frameWidth , frameHeight ) ;
Rectangle textRect = new Rectangle ( 1 , 1 , txt . Width , txt . Height ) ;
bmgr . FillRectangle ( fakeBackgroundBrush , frameRect ) ;
bmgr . DrawImage ( txt , textRect , 0 , 0 , txt . Width , txt . Height , GraphicsUnit . Pixel ) ;
}
}
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddImage ( fkId , fkBm ) ;
2024-07-15 17:39:36 +02:00
}
2024-07-19 15:41:12 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , building . IsPreview ? 0.5f : 1.0f ) ) ;
graphics . DrawImage ( fkBm , buildingRenderBounds , 0 , 0 , fkBm . Width , fkBm . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2024-07-15 17:39:36 +02:00
}
2022-09-13 10:44:15 +02:00
}
}
}
2024-07-03 21:54:25 +02:00
public static void RenderAllRebuildPriorityLabels ( Graphics graphics , GameInfo gameInfo , IEnumerable < ( Point topLeft , Building building ) > buildings , Rectangle visibleCells , Size tileSize , double tilescale )
2022-09-13 10:44:15 +02:00
{
2024-07-03 21:54:25 +02:00
Color textColor = Color . Red ;
Color backPaintColor = Color . Black ;
2023-06-18 16:56:33 +02:00
StringFormat stringFormat = new StringFormat
2022-09-13 10:44:15 +02:00
{
Alignment = StringAlignment . Center ,
LineAlignment = StringAlignment . Center
} ;
2024-07-03 21:54:25 +02:00
string classicFont = null ;
bool cropClassicFont = false ;
TeamRemap remapClassicFont = null ;
2024-07-15 17:39:36 +02:00
if ( Globals . TheTilesetManager is TilesetManagerClassic tsmc & & Globals . TheTeamColorManager is TeamRemapManager trm )
2024-07-03 21:54:25 +02:00
{
2024-07-15 17:39:36 +02:00
classicFont = gameInfo . GetClassicFontInfo ( ClassicFont . CellTriggers , tsmc , trm , textColor , out cropClassicFont , out remapClassicFont ) ;
2024-07-03 21:54:25 +02:00
}
foreach ( ( Point topLeft , Building building ) in buildings )
2022-09-13 10:44:15 +02:00
{
2024-07-15 17:39:36 +02:00
if ( building . BasePriority < 0 | | ! visibleCells . IntersectsWith ( new Rectangle ( topLeft , building . Type . Size ) ) )
2024-07-03 21:54:25 +02:00
{
continue ;
}
Size maxSize = building . Type . Size ;
Rectangle buildingRenderBounds = new Rectangle (
new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ,
new Size ( maxSize . Width * tileSize . Width , maxSize . Height * tileSize . Height )
) ;
2023-06-16 21:46:44 +02:00
double tileScaleHor = tileSize . Width / 128.0 ;
2022-09-13 10:44:15 +02:00
string priText = building . BasePriority . ToString ( ) ;
2024-07-03 21:54:25 +02:00
using ( SolidBrush baseBackgroundBrush = new SolidBrush ( Color . FromArgb ( 256 * 2 / 3 , backPaintColor ) ) )
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
2022-09-13 10:44:15 +02:00
{
2024-07-03 21:54:25 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , building . IsPreview ? 0.5f : 1.0f ) ) ;
if ( classicFont = = null )
{
using ( SolidBrush baseTextBrush = new SolidBrush ( Color . FromArgb ( 255 , textColor ) ) )
using ( Font font = graphics . GetAdjustedFont ( priText , SystemFonts . DefaultFont , buildingRenderBounds . Width , buildingRenderBounds . Height ,
Math . Max ( 1 , ( int ) Math . Round ( 12 * tileScaleHor ) ) , Math . Max ( 1 , ( int ) Math . Round ( 24 * tileScaleHor ) ) , stringFormat , true ) )
{
SizeF textBounds = graphics . MeasureString ( priText , font , buildingRenderBounds . Width , stringFormat ) ;
RectangleF backgroundBounds = new RectangleF ( new PointF ( buildingRenderBounds . X , buildingRenderBounds . Y ) , textBounds ) ;
backgroundBounds . Offset ( ( buildingRenderBounds . Width - textBounds . Width ) / 2.0f , buildingRenderBounds . Height - textBounds . Height ) ;
graphics . FillRectangle ( baseBackgroundBrush , backgroundBounds ) ;
graphics . DrawString ( priText , font , baseTextBrush , backgroundBounds , stringFormat ) ;
}
}
else
2022-09-13 10:44:15 +02:00
{
2024-07-19 15:41:12 +02:00
string priId = "priority_classic_" + maxSize . Width + "x" + maxSize . Height + "_" + priText ;
Bitmap priBm = Globals . TheShapeCacheManager . GetImage ( priId ) ;
if ( priBm = = null )
2024-07-03 21:54:25 +02:00
{
2024-07-19 15:41:12 +02:00
Rectangle buildingBounds = new Rectangle ( Point . Empty , new Size ( maxSize . Width * Globals . OriginalTileWidth , maxSize . Height * Globals . OriginalTileHeight ) ) ;
priBm = new Bitmap ( buildingBounds . Width , buildingBounds . Height ) ;
using ( Graphics bmgr = Graphics . FromImage ( priBm ) )
2024-07-03 21:54:25 +02:00
{
int [ ] indices = Encoding . ASCII . GetBytes ( priText ) . Select ( x = > ( int ) x ) . ToArray ( ) ;
using ( Bitmap txt = RenderTextFromSprite ( classicFont , remapClassicFont , Size . Empty , indices , false , cropClassicFont ) )
{
int textOffsetX = ( buildingBounds . Width - txt . Width ) / 2 ;
int textOffsetY = ( buildingBounds . Height - txt . Height - 1 ) ;
int frameOffsetX = Math . Max ( textOffsetX , - tileSize . Width ) - 1 ;
int frameOffsetY = Math . Max ( textOffsetY , - tileSize . Height ) - 1 ;
int frameWidth = Math . Min ( txt . Width + 2 , buildingBounds . Width ) ;
int frameHeight = Math . Min ( txt . Height + 2 , buildingBounds . Height ) ;
Rectangle frameRect = new Rectangle ( frameOffsetX , frameOffsetY , frameWidth , frameHeight ) ;
Rectangle textRect = new Rectangle ( textOffsetX , textOffsetY , txt . Width , txt . Height ) ;
bmgr . FillRectangle ( baseBackgroundBrush , frameRect ) ;
bmgr . DrawImage ( txt , textRect , 0 , 0 , txt . Width , txt . Height , GraphicsUnit . Pixel ) ;
}
}
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddImage ( priId , priBm ) ;
2024-07-03 21:54:25 +02:00
}
2024-07-19 15:41:12 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , building . IsPreview ? 0.5f : 1.0f ) ) ;
graphics . DrawImage ( priBm , buildingRenderBounds , 0 , 0 , priBm . Width , priBm . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2022-09-13 10:44:15 +02:00
}
}
}
}
2024-03-29 11:00:09 +01:00
public static void RenderAllTechnoTriggers ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , MapLayerFlag layersToRender )
2022-10-03 00:03:30 +02:00
{
2024-08-02 21:26:29 +02:00
RenderAllTechnoTriggers ( graphics , gameInfo , map . Technos , map . Buildings , visibleCells , tileSize , layersToRender , Color . LimeGreen , null , false ) ;
2022-10-03 00:03:30 +02:00
}
2024-08-02 21:26:29 +02:00
public static void RenderAllTechnoTriggers ( Graphics graphics , GameInfo gameInfo , OccupierSet < ICellOccupier > mapTechnos , OccupierSet < ICellOccupier > mapBuildings , Rectangle visibleCells , Size tileSize , MapLayerFlag layersToRender , Color color , string toPick , bool excludePick )
2022-09-13 10:44:15 +02:00
{
2024-08-12 16:18:21 +02:00
string classicFontLarge = null ;
bool cropClassicFontLarge = false ;
string classicFontSmall = null ;
bool cropClassicFontSmall = false ;
TeamRemap remapClassicFontLarge = null ;
TeamRemap remapClassicFontSmall = null ;
2024-07-29 10:08:56 +02:00
if ( Globals . TheTilesetManager is TilesetManagerClassic tsmc & & Globals . TheTeamColorManager is TeamRemapManager trm )
{
2024-08-12 16:18:21 +02:00
classicFontLarge = gameInfo . GetClassicFontInfo ( ClassicFont . TechnoTriggers , tsmc , trm , color , out cropClassicFontLarge , out remapClassicFontLarge ) ;
classicFontSmall = gameInfo . GetClassicFontInfo ( ClassicFont . TechnoTriggersSmall , tsmc , trm , color , out cropClassicFontSmall , out remapClassicFontSmall ) ;
2024-07-29 10:08:56 +02:00
}
2023-06-16 21:46:44 +02:00
double tileScaleHor = tileSize . Width / 128.0 ;
2022-09-13 10:44:15 +02:00
float borderSize = Math . Max ( 0.5f , tileSize . Width / 60.0f ) ;
2024-09-18 23:45:30 +02:00
// The "bounds" in this are a bit weird; they have the real paint position, but the size is relative to Globals.OriginalTileSize
2024-08-02 21:26:29 +02:00
List < ( string trigger , Rectangle bounds , int alpha ) > allTriggers = new List < ( string trigger , Rectangle bounds , int alpha ) > ( ) ;
if ( mapTechnos ! = null )
2023-06-18 16:56:33 +02:00
{
2024-08-02 21:26:29 +02:00
foreach ( ( Point topLeft , ICellOccupier techno ) in mapTechnos )
2022-09-13 10:44:15 +02:00
{
2024-08-02 21:26:29 +02:00
Point location = new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ;
( string trigger , Rectangle bounds , int alpha ) [ ] triggers = null ;
if ( techno is Terrain terrain & & ! Trigger . IsEmpty ( terrain . Trigger ) )
2022-09-13 10:44:15 +02:00
{
2024-08-02 21:26:29 +02:00
if ( layersToRender . HasFlag ( MapLayerFlag . Terrain ) )
2023-06-23 02:35:01 +02:00
{
2024-08-02 21:26:29 +02:00
if ( visibleCells . IntersectsWith ( new Rectangle ( topLeft , terrain . Type . Size ) ) )
{
2024-08-12 16:18:21 +02:00
Size size = new Size ( terrain . Type . Size . Width * Globals . OriginalTileWidth , terrain . Type . Size . Height * Globals . OriginalTileHeight ) ;
triggers = new ( string , Rectangle , int ) [ ] { ( terrain . Trigger , new Rectangle ( location , size ) ,
terrain . IsPreview ? Globals . PreviewAlphaInt : 256 ) } ;
2024-08-02 21:26:29 +02:00
}
2023-06-23 02:35:01 +02:00
}
2022-09-13 10:44:15 +02:00
}
2024-08-02 21:26:29 +02:00
else if ( techno is Unit unit & & ! Trigger . IsEmpty ( unit . Trigger ) )
2022-09-13 10:44:15 +02:00
{
2024-08-02 21:26:29 +02:00
if ( layersToRender . HasFlag ( MapLayerFlag . Units ) )
2023-06-23 02:35:01 +02:00
{
2024-08-02 21:26:29 +02:00
if ( visibleCells . Contains ( topLeft ) )
{
2024-08-12 16:18:21 +02:00
triggers = new ( string , Rectangle , int ) [ ] { ( unit . Trigger , new Rectangle ( location , Globals . OriginalTileSize ) ,
unit . IsPreview ? Globals . PreviewAlphaInt : 256 ) } ;
2024-08-02 21:26:29 +02:00
}
2023-06-23 02:35:01 +02:00
}
2022-09-13 10:44:15 +02:00
}
2024-08-02 21:26:29 +02:00
else if ( techno is InfantryGroup infantryGroup )
2022-09-13 10:44:15 +02:00
{
2024-08-02 21:26:29 +02:00
if ( layersToRender . HasFlag ( MapLayerFlag . Infantry ) )
2023-06-23 02:35:01 +02:00
{
2024-08-02 21:26:29 +02:00
if ( ! visibleCells . Contains ( topLeft ) )
2022-09-13 10:44:15 +02:00
{
2023-06-18 16:56:33 +02:00
continue ;
}
2024-08-13 15:34:23 +02:00
Size size = tileSize ;
Size boundSize = Globals . OriginalTileSize ;
2024-08-02 21:26:29 +02:00
List < ( string , Rectangle , int ) > infantryTriggers = new List < ( string , Rectangle , int ) > ( ) ;
for ( int i = 0 ; i < infantryGroup . Infantry . Length ; + + i )
2023-06-18 16:56:33 +02:00
{
2024-08-02 21:26:29 +02:00
Infantry infantry = infantryGroup . Infantry [ i ] ;
if ( infantry = = null | | Trigger . IsEmpty ( infantry . Trigger ) )
{
continue ;
}
Size offset = Size . Empty ;
switch ( ( InfantryStoppingType ) i )
{
case InfantryStoppingType . UpperLeft :
offset . Width = - size . Width / 4 ;
offset . Height = - size . Height / 4 ;
break ;
case InfantryStoppingType . UpperRight :
offset . Width = size . Width / 4 ;
offset . Height = - size . Height / 4 ;
break ;
case InfantryStoppingType . LowerLeft :
offset . Width = - size . Width / 4 ;
offset . Height = size . Height / 4 ;
break ;
case InfantryStoppingType . LowerRight :
offset . Width = size . Width / 4 ;
offset . Height = size . Height / 4 ;
break ;
}
2024-08-13 15:34:23 +02:00
Rectangle bounds = new Rectangle ( location + offset , boundSize ) ;
2024-08-12 16:18:21 +02:00
infantryTriggers . Add ( ( infantry . Trigger , bounds , infantry . IsPreview ? Globals . PreviewAlphaInt : 256 ) ) ;
2022-09-13 10:44:15 +02:00
}
2024-08-02 21:26:29 +02:00
triggers = infantryTriggers . ToArray ( ) ;
2022-09-13 10:44:15 +02:00
}
2024-08-02 21:26:29 +02:00
}
if ( triggers ! = null )
{
allTriggers . AddRange ( triggers ) ;
2022-09-13 10:44:15 +02:00
}
2023-06-18 16:56:33 +02:00
}
2024-08-02 21:26:29 +02:00
}
if ( mapBuildings ! = null )
{
foreach ( ( Point topLeft , ICellOccupier techno ) in mapBuildings )
2023-03-10 13:40:04 +01:00
{
2024-08-02 21:26:29 +02:00
Point location = new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) ;
if ( techno is Building building & & ! Trigger . IsEmpty ( building . Trigger ) )
2022-09-13 10:44:15 +02:00
{
2024-08-02 21:26:29 +02:00
if ( layersToRender . HasFlag ( MapLayerFlag . Buildings ) )
2022-09-13 10:44:15 +02:00
{
2024-08-02 21:26:29 +02:00
if ( visibleCells . IntersectsWith ( new Rectangle ( topLeft , building . Type . Size ) ) )
{
2024-08-12 16:18:21 +02:00
Size size = new Size ( building . Type . Size . Width * Globals . OriginalTileWidth , building . Type . Size . Height * Globals . OriginalTileHeight ) ;
allTriggers . Add ( ( building . Trigger , new Rectangle ( location , size ) ,
building . IsPreview ? Globals . PreviewAlphaInt : 256 ) ) ;
2024-08-02 21:26:29 +02:00
}
2022-09-13 10:44:15 +02:00
}
}
}
}
2024-08-02 21:26:29 +02:00
StringFormat stringFormat = new StringFormat
{
Alignment = StringAlignment . Center ,
LineAlignment = StringAlignment . Center
} ;
foreach ( ( string trigger , Rectangle bounds , int alpha ) in allTriggers . Where ( x = > toPick = = null
| | ( excludePick & & ! x . trigger . Equals ( toPick , StringComparison . OrdinalIgnoreCase ) )
| | ( ! excludePick & & x . trigger . Equals ( toPick , StringComparison . OrdinalIgnoreCase ) ) ) )
{
2024-08-12 16:18:21 +02:00
// Larger than a single cell.
bool isLarge = bounds . Width > Globals . OriginalTileWidth ;
string classicFont = isLarge ? classicFontLarge : classicFontSmall ;
bool cropClassicFont = isLarge ? cropClassicFontLarge : cropClassicFontSmall ;
TeamRemap remapClassicFont = isLarge ? remapClassicFontLarge : remapClassicFontSmall ;
Color alphaColor = Color . FromArgb ( alpha . Restrict ( 0 , 255 ) , color ) ;
if ( classicFont = = null )
{
int width = bounds . Width * tileSize . Width / Globals . OriginalTileWidth ;
int height = bounds . Height * tileSize . Height / Globals . OriginalTileHeight ;
Rectangle realBounds = new Rectangle ( bounds . Location , new Size ( width , height ) ) ;
using ( SolidBrush technoTriggerBackgroundBrush = new SolidBrush ( Color . FromArgb ( ( 96 * alpha / 256 ) . Restrict ( 0 , 255 ) , Color . Black ) ) )
using ( SolidBrush technoTriggerBrush = new SolidBrush ( alphaColor ) )
using ( Pen technoTriggerPen = new Pen ( alphaColor , borderSize ) )
using ( Font font = graphics . GetAdjustedFont ( trigger , SystemFonts . DefaultFont , width , height ,
Math . Max ( 1 , ( int ) Math . Round ( 12 * tileScaleHor ) ) , Math . Max ( 1 , ( int ) Math . Round ( 24 * tileScaleHor ) ) , stringFormat , true ) )
{
SizeF textBounds = graphics . MeasureString ( trigger , font , width , stringFormat ) ;
RectangleF backgroundBounds = new RectangleF ( bounds . Location , textBounds ) ;
backgroundBounds . Offset ( ( width - textBounds . Width ) / 2.0f , ( height - textBounds . Height ) / 2.0f ) ;
graphics . FillRectangle ( technoTriggerBackgroundBrush , backgroundBounds ) ;
graphics . DrawRectangle ( technoTriggerPen , Rectangle . Round ( backgroundBounds ) ) ;
graphics . DrawString ( trigger , font , technoTriggerBrush , realBounds , stringFormat ) ;
}
}
else
2024-08-02 21:26:29 +02:00
{
2024-08-12 16:18:21 +02:00
int [ ] indices = Encoding . ASCII . GetBytes ( trigger ) . Select ( x = > ( int ) x ) . ToArray ( ) ;
using ( SolidBrush technoTriggerBackgroundBrush = new SolidBrush ( Color . FromArgb ( 96 , Color . Black ) ) )
using ( Pen technoTriggerPen = new Pen ( color , 1 ) )
using ( Bitmap txt = RenderTextFromSprite ( classicFont , remapClassicFont , Size . Empty , indices , false , cropClassicFont ) )
using ( Bitmap txt2 = new Bitmap ( txt . Width + 4 , txt . Height + 4 ) )
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
{
txt2 . SetResolution ( 96 , 96 ) ;
using ( Graphics txt2g = Graphics . FromImage ( txt2 ) )
{
txt2g . FillRectangle ( technoTriggerBackgroundBrush , new Rectangle ( 1 , 1 , txt2 . Width - 2 , txt2 . Height - 2 ) ) ;
txt2g . DrawRectangle ( technoTriggerPen , new Rectangle ( 0 , 0 , txt2 . Width - 1 , txt2 . Height - 1 ) ) ;
txt2g . DrawImage ( txt , new Rectangle ( 2 , 2 , txt . Width , txt . Height ) ) ;
}
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , alpha / 256.0f ) ) ;
int paintOffsX = ( bounds . Width - txt2 . Width ) / 2 * tileSize . Width / Globals . OriginalTileWidth ;
int paintOffsY = ( bounds . Height - txt2 . Height ) / 2 * tileSize . Width / Globals . OriginalTileWidth ;
int textWidth = txt2 . Width * tileSize . Width / Globals . OriginalTileWidth ;
int textHeight = txt2 . Height * tileSize . Width / Globals . OriginalTileWidth ;
Rectangle paintBounds = new Rectangle ( bounds . Location , new Size ( textWidth , textHeight ) ) ;
paintBounds . Offset ( new Point ( paintOffsX , paintOffsY ) ) ;
graphics . DrawImage ( txt2 , paintBounds , 0 , 0 , txt2 . Width , txt2 . Height , GraphicsUnit . Pixel , imageAttributes ) ;
}
2024-08-02 21:26:29 +02:00
}
}
2022-09-13 10:44:15 +02:00
}
2024-07-02 18:39:48 +02:00
public static void RenderWayPointIndicators ( Graphics graphics , Map map , GameInfo gameInfo , Rectangle visibleCells , Size tileSize , Color textColor , bool forPreview , bool excludeSpecified , params Waypoint [ ] specified )
2022-09-22 01:26:46 +02:00
{
HashSet < Waypoint > specifiedWaypoints = specified . ToHashSet ( ) ;
Waypoint [ ] toPaint = excludeSpecified ? map . Waypoints : specified ;
2024-07-15 17:39:36 +02:00
string classicFontShort = null ;
bool cropClassicFontShort = false ;
TeamRemap remapClassicFontShort = null ;
string classicFontLong = null ;
bool cropClassicFontLong = false ;
TeamRemap remapClassicFontLong = null ;
if ( Globals . TheTilesetManager is TilesetManagerClassic tsmc & & Globals . TheTeamColorManager is TeamRemapManager trm )
{
classicFontShort = gameInfo . GetClassicFontInfo ( ClassicFont . Waypoints , tsmc , trm , textColor , out cropClassicFontShort , out remapClassicFontShort ) ;
classicFontLong = gameInfo . GetClassicFontInfo ( ClassicFont . WaypointsLong , tsmc , trm , textColor , out cropClassicFontLong , out remapClassicFontLong ) ;
}
2023-06-18 16:56:33 +02:00
foreach ( Waypoint waypoint in toPaint )
2022-09-22 01:26:46 +02:00
{
2024-07-03 21:54:25 +02:00
if ( ( excludeSpecified & & specifiedWaypoints . Contains ( waypoint ) ) | | ! waypoint . Cell . HasValue
| | ! map . Metrics . GetLocation ( waypoint . Cell . Value , out Point topLeft ) | | ! visibleCells . Contains ( topLeft ) )
2022-09-22 01:26:46 +02:00
{
2024-07-03 21:54:25 +02:00
continue ;
2022-09-22 01:26:46 +02:00
}
2024-07-03 21:54:25 +02:00
Rectangle paintBounds = new Rectangle ( new Point ( topLeft . X * tileSize . Width , topLeft . Y * tileSize . Height ) , tileSize ) ;
string wpText = waypoint . Name ;
2024-07-15 17:39:36 +02:00
bool isLong = wpText . Length > 3 ;
string classicFont = isLong ? classicFontLong : classicFontShort ;
bool cropClassicFont = isLong ? cropClassicFontLong : cropClassicFontShort ;
TeamRemap remapClassicFont = isLong ? remapClassicFontLong : remapClassicFontShort ;
if ( classicFont ! = null & & isLong )
2022-09-22 01:26:46 +02:00
{
2024-07-03 21:54:25 +02:00
wpText = waypoint . ShortName ;
}
Color backPaintColor = Color . FromArgb ( 128 , Color . Black ) ;
// Adjust calcuations to tile size. The below adjustments are done assuming the tile is 128 wide.
double tileScaleHor = tileSize . Width / 128.0 ;
using ( SolidBrush baseBackgroundBrush = new SolidBrush ( backPaintColor ) )
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
{
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , forPreview ? 0.5f : 1.0f ) ) ;
2024-07-02 18:39:48 +02:00
if ( classicFont = = null )
{
StringFormat stringFormat = new StringFormat
{
Alignment = StringAlignment . Center ,
LineAlignment = StringAlignment . Center
} ;
2024-07-03 21:54:25 +02:00
using ( SolidBrush baseTextBrush = new SolidBrush ( Color . FromArgb ( forPreview ? 128 : 255 , textColor ) ) )
using ( Font font = graphics . GetAdjustedFont ( wpText , SystemFonts . DefaultFont , paintBounds . Width , paintBounds . Height ,
2024-07-02 18:39:48 +02:00
Math . Max ( 1 , ( int ) Math . Round ( 12 * tileScaleHor ) ) , Math . Max ( 1 , ( int ) Math . Round ( 55 * tileScaleHor ) ) , stringFormat , true ) )
{
2024-07-03 21:54:25 +02:00
SizeF textBounds = graphics . MeasureString ( wpText , font , paintBounds . Width , stringFormat ) ;
RectangleF backgroundBounds = new RectangleF ( paintBounds . Location , textBounds ) ;
backgroundBounds . Offset ( ( paintBounds . Width - textBounds . Width ) / 2.0f , ( paintBounds . Height - textBounds . Height ) / 2.0f ) ;
graphics . FillRectangle ( baseBackgroundBrush , backgroundBounds ) ;
graphics . DrawString ( wpText , font , baseTextBrush , backgroundBounds , stringFormat ) ;
2024-07-02 18:39:48 +02:00
}
}
else
{
2024-07-19 15:41:12 +02:00
string wpId = "waypoint_" + wpText + "_" + classicFont + "_" + remapClassicFont . Name ;
Bitmap wpBm = Globals . TheShapeCacheManager . GetImage ( wpId ) ;
if ( wpBm = = null )
2024-07-02 18:39:48 +02:00
{
2024-07-19 15:41:12 +02:00
wpBm = new Bitmap ( tileSize . Width , tileSize . Height ) ;
int [ ] indices = Encoding . ASCII . GetBytes ( wpText ) . Select ( x = > ( int ) x ) . ToArray ( ) ;
using ( Graphics bmgr = Graphics . FromImage ( wpBm ) )
using ( Bitmap txt = RenderTextFromSprite ( classicFont , remapClassicFont , Size . Empty , indices , false , cropClassicFont ) )
2024-07-03 21:54:25 +02:00
{
2024-07-19 15:41:12 +02:00
int textOffsetX = ( tileSize . Width - txt . Width ) / 2 ;
int textOffsetY = ( tileSize . Height - txt . Height ) / 2 ;
int frameOffsetX = Math . Max ( textOffsetX , - tileSize . Width ) - 1 ;
int frameOffsetY = Math . Max ( textOffsetY , - tileSize . Height ) - 1 ;
int frameWidth = Math . Min ( txt . Width + 2 , tileSize . Width * 3 ) ;
int frameHeight = Math . Min ( txt . Height + 2 , tileSize . Height * 3 ) ;
Rectangle frameRect = new Rectangle ( frameOffsetX , frameOffsetY , frameWidth , frameHeight ) ;
Rectangle textRect = new Rectangle ( textOffsetX , textOffsetY , txt . Width , txt . Height ) ;
bmgr . FillRectangle ( baseBackgroundBrush , frameRect ) ;
bmgr . DrawImage ( txt , textRect , 0 , 0 , txt . Width , txt . Height , GraphicsUnit . Pixel ) ;
2024-07-03 21:54:25 +02:00
}
2024-07-02 18:39:48 +02:00
}
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddImage ( wpId , wpBm ) ;
Rectangle paintRect = new Rectangle ( paintBounds . Location . X , paintBounds . Location . Y , wpBm . Width , wpBm . Height ) ;
graphics . DrawImage ( wpBm , paintRect , 0 , 0 , wpBm . Width , wpBm . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2024-07-02 18:39:48 +02:00
}
2022-09-22 01:26:46 +02:00
}
}
}
2023-07-05 21:49:17 +02:00
public static void RenderAllBuildingEffectRadiuses ( Graphics graphics , Map map , Rectangle visibleCells , Size tileSize , int effectRadius , Building selected )
2023-03-12 02:40:33 +01:00
{
2023-06-18 16:56:33 +02:00
foreach ( ( Point topLeft , Building building ) in map . Buildings . OfType < Building > ( )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
. Where ( b = > ( b . Occupier . Type . Flag & BuildingTypeFlag . GapGenerator ) ! = BuildingTypeFlag . None ) )
2023-03-12 02:40:33 +01:00
{
2023-07-05 21:49:17 +02:00
RenderBuildingEffectRadius ( graphics , visibleCells , tileSize , effectRadius , building , topLeft , selected ) ;
2023-03-16 19:03:14 +01:00
}
}
2023-03-12 02:40:33 +01:00
2023-07-05 21:49:17 +02:00
public static void RenderBuildingEffectRadius ( Graphics graphics , Rectangle visibleCells , Size tileSize , int effectRadius , Building building , Point topLeft , Building selected )
2023-03-16 19:03:14 +01:00
{
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
if ( ( building . Type . Flag & BuildingTypeFlag . GapGenerator ) ! = BuildingTypeFlag . GapGenerator )
2023-03-16 19:03:14 +01:00
{
return ;
2023-03-12 17:30:08 +01:00
}
2023-06-30 09:23:31 +02:00
ITeamColor tc = Globals . TheTeamColorManager [ building . House ? . BuildingTeamColor ] ;
Color circleColor = Globals . TheTeamColorManager . GetBaseColor ( tc ? . Name ) ;
2023-03-16 19:03:14 +01:00
bool [ , ] cells = building . Type . BaseOccupyMask ;
int maskY = cells . GetLength ( 0 ) ;
int maskX = cells . GetLength ( 1 ) ;
2023-06-23 02:35:01 +02:00
Rectangle circleCellBounds = GeneralUtils . GetBoxFromCenterCell ( topLeft , maskX , maskY , effectRadius , effectRadius , new Size ( 1 , 1 ) , out _ ) ;
2023-03-16 19:03:14 +01:00
Rectangle circleBounds = GeneralUtils . GetBoxFromCenterCell ( topLeft , maskX , maskY , effectRadius , effectRadius , tileSize , out Point center ) ;
2023-06-23 02:35:01 +02:00
if ( visibleCells . IntersectsWith ( circleCellBounds ) )
{
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( building . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
if ( ! building . IsPrebuilt )
{
alphaFactor * = Globals . UnbuiltAlphaFloat ;
}
int alphaFactorInt = ( int ) Math . Round ( alphaFactor * 256 ) . Restrict ( 0 , 255 ) ;
Color alphacorr = Color . FromArgb ( alphaFactorInt , circleColor ) ;
2023-06-23 02:35:01 +02:00
RenderCircleDiagonals ( graphics , tileSize , alphacorr , effectRadius , effectRadius , center ) ;
DrawDashesCircle ( graphics , circleBounds , tileSize , alphacorr , true , - 1.25f , 2.5f ) ;
}
2023-03-12 17:30:08 +01:00
}
2023-07-05 21:49:17 +02:00
public static void RenderAllUnitEffectRadiuses ( Graphics graphics , Map map , Rectangle visibleCells , Size tileSize , int jamRadius , Unit selected )
2023-03-12 17:30:08 +01:00
{
2023-06-18 16:56:33 +02:00
foreach ( ( Point topLeft , Unit unit ) in map . Technos . OfType < Unit > ( )
2023-03-12 17:30:08 +01:00
. Where ( b = > ( b . Occupier . Type . Flag & ( UnitTypeFlag . IsGapGenerator | UnitTypeFlag . IsJammer ) ) ! = UnitTypeFlag . None ) )
{
2023-07-05 21:49:17 +02:00
RenderUnitEffectRadius ( graphics , tileSize , jamRadius , unit , topLeft , visibleCells , selected ) ;
2023-03-16 19:03:14 +01:00
}
}
2023-03-12 17:30:08 +01:00
2023-07-05 21:49:17 +02:00
public static void RenderUnitEffectRadius ( Graphics graphics , Size tileSize , int jamRadius , Unit unit , Point cell , Rectangle visibleCells , Unit selected )
2023-03-16 19:03:14 +01:00
{
2024-06-10 18:29:23 +02:00
bool isJammer = unit . Type . Flag . HasFlag ( UnitTypeFlag . IsJammer ) ;
bool isGapGen = unit . Type . Flag . HasFlag ( UnitTypeFlag . IsGapGenerator ) ;
2023-03-16 19:03:14 +01:00
if ( ! isJammer & & ! isGapGen )
{
return ;
}
2023-06-30 09:23:31 +02:00
ITeamColor tc = Globals . TheTeamColorManager [ unit . House ? . BuildingTeamColor ] ;
Color circleColor = Globals . TheTeamColorManager . GetBaseColor ( tc ? . Name ) ;
2024-07-29 10:08:56 +02:00
float alphaFactor = 1.0f ;
if ( unit . IsPreview )
{
alphaFactor * = Globals . PreviewAlphaFloat ;
}
int alphaFactorInt = ( int ) Math . Round ( alphaFactor * 256 ) . Restrict ( 0 , 255 ) ;
Color alphacorr = Color . FromArgb ( alphaFactorInt , circleColor ) ;
2023-03-16 19:03:14 +01:00
if ( isJammer )
{
// uses map's Gap Generator range.
2023-06-23 02:35:01 +02:00
Rectangle circleCellBounds = GeneralUtils . GetBoxFromCenterCell ( cell , 1 , 1 , jamRadius , jamRadius , new Size ( 1 , 1 ) , out _ ) ;
2023-03-16 19:03:14 +01:00
Rectangle circleBounds = GeneralUtils . GetBoxFromCenterCell ( cell , 1 , 1 , jamRadius , jamRadius , tileSize , out Point center ) ;
2023-06-23 02:35:01 +02:00
if ( visibleCells . IntersectsWith ( circleCellBounds ) )
{
RenderCircleDiagonals ( graphics , tileSize , alphacorr , jamRadius , jamRadius , center ) ;
DrawDashesCircle ( graphics , circleBounds , tileSize , alphacorr , true , - 1.25f , 2.5f ) ;
}
2023-03-16 19:03:14 +01:00
}
if ( isGapGen )
{
// uses specific 5x7 circle around the unit cell
int radiusX = 2 ;
int radiusY = 3 ;
2023-06-23 02:35:01 +02:00
Rectangle circleCellBounds = GeneralUtils . GetBoxFromCenterCell ( cell , 1 , 1 , radiusX , radiusY , new Size ( 1 , 1 ) , out _ ) ;
2023-03-16 19:03:14 +01:00
Rectangle circleBounds = GeneralUtils . GetBoxFromCenterCell ( cell , 1 , 1 , radiusX , radiusY , tileSize , out Point center ) ;
2023-06-23 02:35:01 +02:00
if ( visibleCells . IntersectsWith ( circleCellBounds ) )
{
RenderCircleDiagonals ( graphics , tileSize , alphacorr , radiusX , radiusY , center ) ;
DrawDashesCircle ( graphics , circleBounds , tileSize , alphacorr , true , - 1.25f , 2.5f ) ;
}
2023-03-12 17:30:08 +01:00
}
}
private static void RenderCircleDiagonals ( Graphics graphics , Size tileSize , Color paintColor , double radiusX , double radiusY , Point center )
{
float penSize = Math . Max ( 1.0f , tileSize . Width / 16.0f ) ;
using ( Pen linePen = new Pen ( paintColor , penSize ) )
{
linePen . DashPattern = new float [ ] { 1.0F , 4.0F , 6.0F , 4.0F } ;
linePen . DashCap = DashCap . Round ;
2023-06-21 12:18:49 +02:00
int diamX = ( int ) Math . Round ( ( radiusX * 2 + 1 ) * tileSize . Width ) ;
2023-03-12 17:30:08 +01:00
int radX = diamX / 2 ;
2023-06-21 12:18:49 +02:00
int diamY = ( int ) Math . Round ( ( radiusY * 2 + 1 ) * tileSize . Height ) ;
2023-03-12 17:30:08 +01:00
int radY = diamY / 2 ;
double sinDistance = Math . Sin ( Math . PI * 45 / 180.0 ) ;
2023-06-21 12:18:49 +02:00
int sinX = ( int ) Math . Round ( radX * sinDistance ) ;
int sinY = ( int ) Math . Round ( radY * sinDistance ) ;
2023-03-12 17:30:08 +01:00
graphics . DrawLine ( linePen , center , new Point ( center . X , center . Y - radY ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X - sinX , center . Y + sinY ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X + radX , center . Y ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X + sinX , center . Y + sinX ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X , center . Y + radY ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X + sinX , center . Y - sinY ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X - radX , center . Y ) ) ;
graphics . DrawLine ( linePen , center , new Point ( center . X - sinX , center . Y - sinY ) ) ;
2023-03-12 02:40:33 +01:00
}
}
2023-06-23 02:35:01 +02:00
public static void RenderAllWayPointRevealRadiuses ( Graphics graphics , IGamePlugin plugin , Map map , Rectangle visibleCells , Size tileSize , Waypoint selectedItem )
2023-03-12 02:40:33 +01:00
{
2023-06-23 02:35:01 +02:00
RenderAllWayPointRevealRadiuses ( graphics , plugin , map , visibleCells , tileSize , selectedItem , false ) ;
2023-03-16 19:03:14 +01:00
}
2023-06-23 02:35:01 +02:00
public static void RenderAllWayPointRevealRadiuses ( Graphics graphics , IGamePlugin plugin , Map map , Rectangle visibleCells , Size tileSize , Waypoint selectedItem , bool onlySelected )
2023-03-16 19:03:14 +01:00
{
2023-12-14 11:49:44 +01:00
int [ ] wpReveal1 = plugin . GetRevealRadiusForWaypoints ( false ) ;
int [ ] wpReveal2 = plugin . GetRevealRadiusForWaypoints ( true ) ;
2023-03-16 19:03:14 +01:00
Waypoint [ ] allWaypoints = map . Waypoints ;
for ( int i = 0 ; i < allWaypoints . Length ; i + + )
{
Waypoint cur = allWaypoints [ i ] ;
bool isSelected = selectedItem ! = null & & selectedItem = = cur ;
if ( onlySelected & & ! isSelected )
{
continue ;
}
2023-03-12 02:40:33 +01:00
Point ? p = cur ? . Point ;
if ( p . HasValue )
{
Color drawColor = isSelected ? Color . Yellow : Color . Orange ;
if ( wpReveal1 [ i ] ! = 0 )
{
2023-06-23 02:35:01 +02:00
RenderWayPointRevealRadius ( graphics , map . Metrics , visibleCells , tileSize , drawColor , isSelected , false , wpReveal1 [ i ] , cur ) ;
2023-03-12 02:40:33 +01:00
}
if ( wpReveal2 [ i ] ! = 0 )
{
2023-06-23 02:35:01 +02:00
RenderWayPointRevealRadius ( graphics , map . Metrics , visibleCells , tileSize , drawColor , isSelected , false , wpReveal2 [ i ] , cur ) ;
2023-03-12 02:40:33 +01:00
}
}
}
}
2023-07-05 21:49:17 +02:00
public static void RenderWayPointRevealRadius ( Graphics graphics , CellMetrics metrics , Rectangle visibleCells , Size tileSize , Color circleColor , bool isSelected , bool forPreview , double revealRadius , Waypoint waypoint )
2023-03-12 02:40:33 +01:00
{
2023-03-16 19:03:14 +01:00
if ( waypoint . Cell . HasValue & & metrics . GetLocation ( waypoint . Cell . Value , out Point cellPoint ) )
2023-03-12 02:40:33 +01:00
{
double diam = revealRadius * 2 + 1 ;
2023-06-23 02:35:01 +02:00
Rectangle circleCellBounds = new Rectangle (
( int ) Math . Round ( cellPoint . X - revealRadius ) ,
( int ) Math . Round ( cellPoint . Y - revealRadius ) ,
( int ) Math . Round ( diam ) ,
( int ) Math . Round ( diam ) ) ;
2023-03-12 02:40:33 +01:00
Rectangle circleBounds = new Rectangle (
2023-06-21 12:18:49 +02:00
( int ) Math . Round ( cellPoint . X * tileSize . Width - revealRadius * tileSize . Width ) ,
( int ) Math . Round ( cellPoint . Y * tileSize . Width - revealRadius * tileSize . Height ) ,
( int ) Math . Round ( diam * tileSize . Width ) ,
( int ) Math . Round ( diam * tileSize . Height ) ) ;
2023-06-23 02:35:01 +02:00
if ( visibleCells . IntersectsWith ( circleCellBounds ) )
{
2023-07-05 21:49:17 +02:00
DrawDashesCircle ( graphics , circleBounds , tileSize , Color . FromArgb ( isSelected & & ! forPreview ? 255 : 128 , circleColor ) , isSelected , 1.25f , 2.5f ) ;
2023-06-23 02:35:01 +02:00
}
2023-03-12 02:40:33 +01:00
}
}
public static void DrawCircle ( Graphics graphics , Rectangle circleBounds , Size tileSize , Color circleColor , bool thickborder )
{
float penSize = Math . Max ( 1f , tileSize . Width / ( thickborder ? 16.0f : 32.0f ) ) ;
using ( Pen circlePen = new Pen ( circleColor , penSize ) )
{
graphics . DrawEllipse ( circlePen , circleBounds ) ;
}
}
2023-03-14 15:33:40 +01:00
public static void DrawDashesCircle ( Graphics graphics , Rectangle circleBounds , Size tileSize , Color circleColor , bool thickborder , float startAngle , float drawAngle )
2023-03-12 02:40:33 +01:00
{
float penSize = Math . Max ( 1f , tileSize . Width / ( thickborder ? 16.0f : 32.0f ) ) ;
using ( Pen circlePen = new Pen ( circleColor , penSize ) )
{
drawAngle = Math . Abs ( drawAngle ) ;
float endPoint = 360f + startAngle - drawAngle ;
for ( float i = startAngle ; i < = endPoint ; i + = drawAngle * 2 )
{
graphics . DrawArc ( circlePen , circleBounds , i , drawAngle ) ;
}
}
}
2024-05-10 00:03:03 +02:00
public static void RenderCellTriggersSoft ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , params string [ ] specifiedToExclude )
2022-09-13 10:44:15 +02:00
{
2024-03-29 11:00:09 +01:00
RenderCellTriggers ( graphics , gameInfo , map , visibleCells , tileSize , Color . Black , Color . Silver , Color . White , 0.75f , false , true , specifiedToExclude ) ;
2022-09-13 10:44:15 +02:00
}
2024-05-10 00:03:03 +02:00
public static void RenderCellTriggersHard ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , params string [ ] specifiedToExclude )
2022-09-13 10:44:15 +02:00
{
2024-03-29 11:00:09 +01:00
RenderCellTriggers ( graphics , gameInfo , map , visibleCells , tileSize , Color . Black , Color . Silver , Color . White , 1 , false , true , specifiedToExclude ) ;
2023-03-26 18:40:14 +02:00
}
2024-05-10 00:03:03 +02:00
public static void RenderCellTriggersSelected ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , params string [ ] specifiedToDraw )
2023-03-26 18:40:14 +02:00
{
2024-03-29 11:00:09 +01:00
RenderCellTriggers ( graphics , gameInfo , map , visibleCells , tileSize , Color . Black , Color . FromArgb ( 0xFF , 0xC0 , 0xC0 , 0x00 ) , Color . Yellow , 1 , true , false , specifiedToDraw ) ;
2023-03-26 18:40:14 +02:00
}
2024-03-29 11:00:09 +01:00
public static void RenderCellTriggers ( Graphics graphics , GameInfo gameInfo , Map map , Rectangle visibleCells , Size tileSize , Color fillColor , Color borderColor , Color textColor , double alphaAdjust , bool thickborder , bool excludeSpecified , params string [ ] specified )
2023-03-26 18:40:14 +02:00
{
2024-03-29 11:00:09 +01:00
string classicFont = null ;
2024-07-02 18:39:48 +02:00
bool cropClassicFont = false ;
TeamRemap remapClassicFont = null ;
2024-07-15 17:39:36 +02:00
if ( Globals . TheTilesetManager is TilesetManagerClassic tsmc & & Globals . TheTeamColorManager is TeamRemapManager trm )
2024-03-28 19:22:06 +01:00
{
2024-07-15 17:39:36 +02:00
classicFont = gameInfo . GetClassicFontInfo ( ClassicFont . CellTriggers , tsmc , trm , textColor , out cropClassicFont , out remapClassicFont ) ;
2024-03-28 19:22:06 +01:00
}
2024-07-02 18:39:48 +02:00
// For bounds, add one more cell to get all borders showing.
2023-06-23 02:35:01 +02:00
Rectangle boundRenderCells = visibleCells ;
boundRenderCells . Inflate ( 1 , 1 ) ;
boundRenderCells . Intersect ( map . Metrics . Bounds ) ;
HashSet < string > specifiedSet = new HashSet < string > ( specified , StringComparer . OrdinalIgnoreCase ) ;
List < ( Point p , CellTrigger cellTrigger ) > toRender = new List < ( Point p , CellTrigger cellTrigger ) > ( ) ;
HashSet < string > toRenderSet = new HashSet < string > ( ) ;
List < ( Point p , CellTrigger cellTrigger ) > boundsToDraw = new List < ( Point p , CellTrigger cellTrigger ) > ( ) ;
2024-03-28 19:22:06 +01:00
foreach ( ( int cell , CellTrigger cellTrigger ) in map . CellTriggers . OrderBy ( c = > c . Cell ) )
2023-06-23 02:35:01 +02:00
{
2024-03-28 19:22:06 +01:00
int x = cell % map . Metrics . Width ;
int y = cell / map . Metrics . Width ;
2023-06-23 02:35:01 +02:00
if ( ! boundRenderCells . Contains ( x , y ) )
{
continue ;
}
bool contains = specifiedSet . Contains ( cellTrigger . Trigger ) ;
if ( contains & & excludeSpecified | | ! contains & & ! excludeSpecified )
{
continue ;
}
2024-03-28 19:22:06 +01:00
// Allow better alpha control by detecting previews but not using that alpha.
2024-07-29 10:08:56 +02:00
bool isPreview = cellTrigger . IsPreview ;
2023-06-23 02:35:01 +02:00
Point p = new Point ( x , y ) ;
if ( visibleCells . Contains ( x , y ) )
{
toRender . Add ( ( p , cellTrigger ) ) ;
toRenderSet . Add ( cellTrigger . Trigger + "=" + ( isPreview ? 'P' : 'N' ) ) ;
}
boundsToDraw . Add ( ( p , cellTrigger ) ) ;
}
if ( boundsToDraw . Count = = 0 )
{
return ;
}
2023-03-26 18:40:14 +02:00
Color ApplyAlpha ( Color col , int baseAlpha , double alphaMul )
{
return Color . FromArgb ( Math . Max ( 0 , Math . Min ( 0xFF , ( int ) Math . Round ( baseAlpha * alphaMul , MidpointRounding . AwayFromZero ) ) ) , col ) ;
} ;
2024-03-29 11:00:09 +01:00
int sizeW = tileSize . Width ;
int sizeH = tileSize . Height ;
// Actual balance is fixed; border is 1, text is 1/2, background is 3/8. The original alpha inside the given colors is ignored.
2024-07-02 18:39:48 +02:00
// Should probably rewrite this to paint text as opaque on 6/8 alpha background, then paint that as 50% alpha and add solid border,
// and then paint that with the final adjusted alpha factor.
2023-03-26 18:40:14 +02:00
fillColor = ApplyAlpha ( fillColor , 0x60 , alphaAdjust ) ;
2023-07-05 21:49:17 +02:00
borderColor = ApplyAlpha ( borderColor , 0xFF , alphaAdjust ) ;
2023-03-26 18:40:14 +02:00
textColor = ApplyAlpha ( textColor , 0x80 , alphaAdjust ) ;
2024-03-29 11:00:09 +01:00
// for classic fonts
Color adjustColor = ApplyAlpha ( Color . White , 0x80 , alphaAdjust ) ;
// Preview versions
2023-03-26 18:40:14 +02:00
Color previewFillColor = ApplyAlpha ( fillColor , 0x60 , alphaAdjust / 2 ) ;
2023-07-05 21:49:17 +02:00
Color previewBorderColor = ApplyAlpha ( borderColor , 0xFF , alphaAdjust / 2 ) ;
2023-03-26 18:40:14 +02:00
Color previewTextColor = ApplyAlpha ( textColor , 0x80 , alphaAdjust / 2 ) ;
2024-03-29 11:00:09 +01:00
// for classic fonts
2024-03-28 19:22:06 +01:00
Color previewAdjustColor = ApplyAlpha ( Color . White , 0x80 , alphaAdjust / 2 ) ;
2024-03-29 11:00:09 +01:00
2024-03-28 19:22:06 +01:00
// Render each trigger once, and just paint the rendered image multiple times.
Dictionary < string , Bitmap > backRenders = new Dictionary < string , Bitmap > ( ) ;
2023-06-23 02:35:01 +02:00
Dictionary < string , Bitmap > renders = new Dictionary < string , Bitmap > ( ) ;
2024-07-19 15:41:12 +02:00
double tileScaleHor = sizeW / 128.0 ;
Rectangle tileBounds = new Rectangle ( 0 , 0 , sizeW , sizeH ) ;
using ( SolidBrush prevCellTriggersBackgroundBrush = new SolidBrush ( previewFillColor ) )
using ( SolidBrush prevCellTriggersBrush = new SolidBrush ( previewTextColor ) )
using ( SolidBrush cellTriggersBackgroundBrush = new SolidBrush ( fillColor ) )
using ( SolidBrush cellTriggersBrush = new SolidBrush ( textColor ) )
2022-09-13 10:44:15 +02:00
{
2024-07-19 15:41:12 +02:00
foreach ( string trigger in toRenderSet )
2022-09-13 10:44:15 +02:00
{
2024-07-19 15:41:12 +02:00
string [ ] trigPart = trigger . Split ( '=' ) ;
if ( trigPart . Length ! = 2 )
2022-09-13 10:44:15 +02:00
{
2024-07-19 15:41:12 +02:00
continue ;
}
string text = trigPart [ 0 ] ;
bool isPreview = trigPart [ 1 ] = = "P" ;
Color textCol = isPreview ? previewTextColor : textColor ;
Rectangle textBounds = new Rectangle ( Point . Empty , tileBounds . Size ) ;
string trId = "trigger_" + trigger + "_" + ( ( uint ) ( isPreview ? previewTextColor : textColor ) . ToArgb ( ) ) . ToString ( "X4" ) ;
string bgId = "trig_bg_" + trigger + "_" + ( ( uint ) ( isPreview ? previewFillColor : fillColor ) . ToArgb ( ) ) . ToString ( "X4" ) ;
2023-06-23 02:35:01 +02:00
2024-07-19 15:41:12 +02:00
Bitmap trigbm = Globals . TheShapeCacheManager . GetImage ( trId ) ;
if ( trigbm = = null )
{
trigbm = new Bitmap ( sizeW , sizeH ) ;
using ( Graphics trigctg = Graphics . FromImage ( trigbm ) )
2023-06-23 02:35:01 +02:00
{
2024-07-19 15:41:12 +02:00
SetRenderSettings ( trigctg , classicFont = = null ) ;
2023-06-23 02:35:01 +02:00
StringFormat stringFormat = new StringFormat
{
Alignment = StringAlignment . Center ,
LineAlignment = StringAlignment . Center
} ;
2023-07-05 21:49:17 +02:00
using ( Bitmap textBm = new Bitmap ( sizeW , sizeH ) )
2023-06-23 02:35:01 +02:00
{
2023-07-05 21:49:17 +02:00
using ( Graphics textGr = Graphics . FromImage ( textBm ) )
{
2024-07-02 18:39:48 +02:00
if ( classicFont = = null )
2024-03-28 19:22:06 +01:00
{
2024-07-19 15:41:12 +02:00
using ( Font font = trigctg . GetAdjustedFont ( text , SystemFonts . DefaultFont , textBounds . Width , textBounds . Height ,
2024-03-28 19:22:06 +01:00
Math . Max ( 1 , ( int ) Math . Round ( 24 * tileScaleHor ) ) , Math . Max ( 1 , ( int ) Math . Round ( 48 * tileScaleHor ) ) , stringFormat , true ) )
{
2024-07-19 15:41:12 +02:00
SetRenderSettings ( trigctg , true ) ;
2024-03-29 11:00:09 +01:00
// If not set, the text will have ugly black fades at the edges.
2024-03-28 19:22:06 +01:00
textGr . TextRenderingHint = TextRenderingHint . SingleBitPerPixel ;
textGr . DrawString ( text , font , isPreview ? prevCellTriggersBrush : cellTriggersBrush , textBounds , stringFormat ) ;
}
}
else
{
int [ ] indices = Encoding . ASCII . GetBytes ( text ) . Select ( x = > ( int ) x ) . ToArray ( ) ;
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
{
2024-03-29 11:00:09 +01:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( isPreview ? previewAdjustColor : adjustColor , 1.0f , 1.0f ) ) ;
2024-07-02 18:39:48 +02:00
using ( Bitmap txt = RenderTextFromSprite ( classicFont , remapClassicFont , tileBounds . Size , indices , false , cropClassicFont ) )
2024-03-28 19:22:06 +01:00
{
Rectangle paintBounds = new Rectangle ( Point . Empty , tileSize ) ;
2024-07-19 15:41:12 +02:00
textGr . DrawImage ( txt , textBounds , 0 , 0 , tileBounds . Width , tileBounds . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2024-03-28 19:22:06 +01:00
}
}
}
2023-07-05 21:49:17 +02:00
}
2024-07-19 15:41:12 +02:00
trigctg . DrawImage ( textBm , 0 , 0 ) ;
2023-06-23 02:35:01 +02:00
}
}
2024-07-19 15:41:12 +02:00
Globals . TheShapeCacheManager . AddImage ( trId , trigbm ) ;
2022-09-13 10:44:15 +02:00
}
2024-07-19 15:41:12 +02:00
Bitmap fillbm = Globals . TheShapeCacheManager . GetImage ( bgId ) ;
if ( fillbm = = null )
2024-03-28 19:22:06 +01:00
{
2024-07-19 15:41:12 +02:00
fillbm = new Bitmap ( sizeW , sizeH ) ;
using ( Graphics fillctg = Graphics . FromImage ( fillbm ) )
{
SetRenderSettings ( fillctg , classicFont = = null ) ;
fillctg . FillRectangle ( isPreview ? prevCellTriggersBackgroundBrush : cellTriggersBackgroundBrush , textBounds ) ;
// Clear background under text to make it more transparent. There are probably more elegant ways to do this, but this works.
RegionData textInline = ImageUtils . GetOutline ( new Size ( sizeW , sizeH ) , trigbm , 0.00f , ( byte ) Math . Max ( 0 , textCol . A - 1 ) , false ) ;
using ( Region clearArea = new Region ( textInline ) )
using ( Brush clear = new SolidBrush ( Color . Transparent ) )
{
fillctg . CompositingMode = CompositingMode . SourceCopy ;
fillctg . FillRegion ( clear , clearArea ) ;
fillctg . CompositingMode = CompositingMode . SourceOver ;
}
}
Globals . TheShapeCacheManager . AddImage ( bgId , fillbm ) ;
2024-03-28 19:22:06 +01:00
}
2024-07-19 15:41:12 +02:00
backRenders . Add ( trigger , fillbm ) ;
renders . Add ( trigger , trigbm ) ;
2024-03-28 19:22:06 +01:00
}
2024-07-19 15:41:12 +02:00
}
2024-03-28 19:22:06 +01:00
2024-07-19 15:41:12 +02:00
var backupCompositingQuality = graphics . CompositingQuality ;
var backupInterpolationMode = graphics . InterpolationMode ;
var backupSmoothingMode = graphics . SmoothingMode ;
var backupPixelOffsetMode = graphics . PixelOffsetMode ;
SetRenderSettings ( graphics , classicFont = = null ) ;
foreach ( ( Point p , CellTrigger cellTrigger ) in toRender )
{
2024-07-29 10:08:56 +02:00
bool isPreview = cellTrigger . IsPreview ;
2024-07-19 15:41:12 +02:00
string requestName = cellTrigger . Trigger + "=" + ( isPreview ? 'P' : 'N' ) ;
if ( backRenders . TryGetValue ( requestName , out Bitmap fillCtBm ) )
2024-03-28 19:22:06 +01:00
{
2024-07-19 15:41:12 +02:00
graphics . DrawImage ( fillCtBm , p . X * tileSize . Width , p . Y * tileSize . Height ) ;
2024-03-28 19:22:06 +01:00
}
2024-07-19 15:41:12 +02:00
}
2024-03-28 19:22:06 +01:00
2024-07-19 15:41:12 +02:00
float borderSize = Math . Max ( 0.5f , tileSize . Width / 60.0f ) ;
float thickBorderSize = Math . Max ( 1f , tileSize . Width / 20.0f ) ;
using ( Pen prevBorderPen = new Pen ( previewBorderColor , thickborder ? thickBorderSize : borderSize ) )
using ( Pen borderPen = new Pen ( borderColor , thickborder ? thickBorderSize : borderSize ) )
{
foreach ( ( Point p , CellTrigger cellTrigger ) in boundsToDraw )
2023-06-23 02:35:01 +02:00
{
2024-07-29 10:08:56 +02:00
bool isPreview = cellTrigger . IsPreview ;
2024-07-19 15:41:12 +02:00
Rectangle bounds = new Rectangle ( new Point ( p . X * tileSize . Width , p . Y * tileSize . Height ) , tileSize ) ;
graphics . DrawRectangle ( isPreview ? prevBorderPen : borderPen , bounds ) ;
2022-09-13 10:44:15 +02:00
}
}
2024-07-19 15:41:12 +02:00
foreach ( ( Point p , CellTrigger cellTrigger ) in toRender )
2023-06-23 02:35:01 +02:00
{
2024-07-29 10:08:56 +02:00
bool isPreview = cellTrigger . IsPreview ;
2024-07-19 15:41:12 +02:00
string requestName = cellTrigger . Trigger + "=" + ( isPreview ? 'P' : 'N' ) ;
if ( renders . TryGetValue ( requestName , out Bitmap ctBm ) )
2023-06-23 02:35:01 +02:00
{
2024-07-19 15:41:12 +02:00
graphics . DrawImage ( ctBm , p . X * tileSize . Width , p . Y * tileSize . Height ) ;
2023-06-23 02:35:01 +02:00
}
}
2024-07-19 15:41:12 +02:00
graphics . CompositingQuality = backupCompositingQuality ;
graphics . InterpolationMode = backupInterpolationMode ;
graphics . SmoothingMode = backupSmoothingMode ;
graphics . PixelOffsetMode = backupPixelOffsetMode ;
2022-09-13 10:44:15 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderMapBoundaries ( Graphics graphics , Map map , Rectangle visibleCells , Size tileSize )
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
RenderMapBoundaries ( graphics , map . Bounds , visibleCells , tileSize , Color . Cyan ) ;
2022-09-13 10:44:15 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderMapBoundaries ( Graphics graphics , Rectangle bounds , Rectangle visibleCells , Size tileSize , Color color )
2022-09-13 10:44:15 +02:00
{
2023-06-23 02:35:01 +02:00
// Inflate so you'd see the indicator if it's at the edge.
visibleCells . Inflate ( 1 , 1 ) ;
Rectangle cropped = bounds ;
cropped . Intersect ( visibleCells ) ;
// If these two are identical, that means all map borders are at least one cell outside the visible cells area.
if ( visibleCells = = cropped )
{
return ;
}
2023-06-18 16:56:33 +02:00
Rectangle boundsRect = Rectangle . FromLTRB (
2022-09-13 10:44:15 +02:00
bounds . Left * tileSize . Width ,
bounds . Top * tileSize . Height ,
bounds . Right * tileSize . Width ,
bounds . Bottom * tileSize . Height
) ;
2023-06-18 16:56:33 +02:00
using ( Pen boundsPen = new Pen ( color , Math . Max ( 1f , tileSize . Width / 8.0f ) ) )
2022-09-13 23:44:03 +02:00
{
2023-06-23 02:35:01 +02:00
graphics . DrawRectangle ( boundsPen , boundsRect ) ;
}
}
2022-11-14 23:13:32 +01:00
2023-06-23 02:35:01 +02:00
public static void RenderMapSymmetry ( Graphics graphics , Rectangle bounds , Size tileSize , Color color )
{
Rectangle boundsRect = Rectangle . FromLTRB (
bounds . Left * tileSize . Width ,
bounds . Top * tileSize . Height ,
bounds . Right * tileSize . Width ,
bounds . Bottom * tileSize . Height
) ;
using ( Pen boundsPen = new Pen ( color , Math . Max ( 1f , tileSize . Width / 8.0f ) ) )
{
graphics . DrawLine ( boundsPen , new Point ( boundsRect . X , boundsRect . Y ) , new Point ( boundsRect . Right , boundsRect . Bottom ) ) ;
graphics . DrawLine ( boundsPen , new Point ( boundsRect . Right , boundsRect . Y ) , new Point ( boundsRect . X , boundsRect . Bottom ) ) ;
int halfX = boundsRect . X + boundsRect . Width / 2 ;
int halfY = boundsRect . Y + boundsRect . Height / 2 ;
graphics . DrawLine ( boundsPen , new Point ( halfX , boundsRect . Y ) , new Point ( halfX , boundsRect . Bottom ) ) ;
graphics . DrawLine ( boundsPen , new Point ( boundsRect . X , halfY ) , new Point ( boundsRect . Right , halfY ) ) ;
2022-09-13 23:44:03 +02:00
}
2022-09-13 10:44:15 +02:00
}
2023-06-23 02:35:01 +02:00
public static void RenderMapGrid ( Graphics graphics , Rectangle renderBounds , Rectangle mapBounds , bool mapBoundsRendered , Size tileSize , Color color )
2023-02-24 22:08:01 +01:00
{
2023-06-23 02:35:01 +02:00
Rectangle boundvisibleCells = mapBounds ;
boundvisibleCells . Intersect ( renderBounds ) ;
int startY = boundvisibleCells . Y ;
int startX = boundvisibleCells . X ;
int endRight = boundvisibleCells . Right ;
int endBottom = boundvisibleCells . Bottom ;
if ( mapBoundsRendered )
{
if ( boundvisibleCells . Y = = mapBounds . Y )
{
startY + + ;
}
if ( boundvisibleCells . Bottom = = mapBounds . Bottom )
{
endBottom - - ;
}
if ( boundvisibleCells . X = = mapBounds . X )
{
startX + + ;
}
if ( boundvisibleCells . Right = = mapBounds . Right )
{
endRight - - ;
}
}
2023-06-18 16:56:33 +02:00
using ( Pen gridPen = new Pen ( color , Math . Max ( 1f , tileSize . Width / 16.0f ) ) )
2023-02-24 22:08:01 +01:00
{
2023-06-23 02:35:01 +02:00
int leftBound = boundvisibleCells . X * tileSize . Width ;
int rightBound = boundvisibleCells . Right * tileSize . Width ;
for ( int y = startY ; y < = endBottom ; + + y )
2023-02-24 22:08:01 +01:00
{
int ymul = y * tileSize . Height ;
graphics . DrawLine ( gridPen , new Point ( leftBound , ymul ) , new Point ( rightBound , ymul ) ) ;
}
2023-06-23 02:35:01 +02:00
//*/
int topBound = boundvisibleCells . Y * tileSize . Height ;
int bottomBound = boundvisibleCells . Bottom * tileSize . Height ;
for ( int x = startX ; x < = endRight ; + + x )
2023-02-24 22:08:01 +01:00
{
int xmul = x * tileSize . Height ;
graphics . DrawLine ( gridPen , new Point ( xmul , topBound ) , new Point ( xmul , bottomBound ) ) ;
}
2023-06-23 02:35:01 +02:00
//*/
2023-02-24 22:08:01 +01:00
}
}
2024-05-10 00:03:03 +02:00
/// <summary>
/// Paints the passability grid onto the map.
/// </summary>
/// <param name="graphics">Graphics to paint on.</param>
/// <param name="plugin">Game plugin</param>
/// <param name="templates">The map data itself</param>
2024-06-26 22:18:38 +02:00
/// <param name="technos">If given, draws a green grid on the locations of the technos in the given set.</param>
2024-08-08 15:48:23 +02:00
/// <param name="buildings">If given, draws a green grid on the locations of the buildings in the given set.</param>
2024-05-10 00:03:03 +02:00
/// <param name="tileSize">Tile size</param>
/// <param name="visibleCells">If given, only cells in the given area are marked.</param>
2024-06-26 22:18:38 +02:00
/// <param name="ignoreCells">Cells to completely ignore during the drawing operation.</param>
/// <param name="forPreview">Indicates this is painted for placement preview purposes, meaning colours with their alpha set to 0 are restored and also handled.</param>
/// <param name="soft">True to paint the hashing with only 25% alpha instead of the usual 50%.</param>
2024-08-08 15:48:23 +02:00
public static void RenderHashAreas ( Graphics graphics , IGamePlugin plugin , CellGrid < Template > templates , OccupierSet < ICellOccupier > technos , OccupierSet < ICellOccupier > buildings , Size tileSize , Rectangle visibleCells , HashSet < Point > ignoreCells , bool forPreview , bool soft )
2023-06-22 01:23:52 +02:00
{
// Check which cells need to be marked.
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
LandType clearLand = LandType . Clear ;
2024-05-10 00:03:03 +02:00
// Fetch the terrain type for clear terrain on this theater.
2024-06-26 22:18:38 +02:00
IEnumerable < Point > points = visibleCells . Points ( ) ;
TemplateType clear = plugin . Map . TemplateTypes . Where ( t = > t . Flag . HasFlag ( TemplateTypeFlag . Clear ) ) . FirstOrDefault ( ) ;
clearLand = clear . LandTypes . Length > 0 ? clear . LandTypes [ 0 ] : LandType . Clear ;
HashSet < LandType > usedLandTypes = plugin . Map . UsedLandTypes ;
if ( technos ! = null & & technos . Count ( ) = = 0 )
{
technos = null ;
}
2024-08-08 15:48:23 +02:00
if ( buildings ! = null & & buildings . Count ( ) = = 0 )
{
buildings = null ;
}
2024-06-26 22:18:38 +02:00
if ( templates ! = null & & templates . Length = = 0 )
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
{
2024-06-26 22:18:38 +02:00
templates = null ;
}
2024-08-08 15:48:23 +02:00
if ( technos = = null & & buildings ! = null & & templates = = null )
2024-06-26 22:18:38 +02:00
{
return ;
Prepared for migration to using external ini data for all settings.
* Added support for fan-added theaters available in CnCNet: Snow for TD; Desert, Jungle, Barren and Cave for RA. These only work in classic mode, if their theater mix files are found in the configured classic files folder.
* Mouse zoom is now disabled during drag-scroll operations, since it invariably messed up the position calculations.
* Map templates, Smudge and Overlay are no longer restricted to specific theaters; if they exist in a theater, they are allowed.
* Theater-sensitive civilian buildings are no longer restricted to specific theaters; if they exist in a theater, they are allowed. Military buildings, incuding the theater-sensitive Misile Silo and Pillbox in RA, are always usable, no matter whether the theater has graphics for them.
* On Interior maps, the "ConvertRaObsoleteClear" logic will now generate spots of passable terrain outside the map border on any point where passable terrain touches the border, to allow potential reinforcements to enter the map there.
* Removed all hardcoded special code per vehicle and per building, in preparation for exporting all editor settings to ini.
2023-08-25 18:12:35 +02:00
}
2023-07-06 22:48:29 +02:00
// Caching this in advance for all types.
LandType [ ] landTypes = ( LandType [ ] ) Enum . GetValues ( typeof ( LandType ) ) ;
2024-05-10 00:03:03 +02:00
int tileWidth = tileSize . Width ;
int tileHeight = tileSize . Height ;
float lineSize = tileWidth / 16.0f ;
int lineOffsetW = tileWidth / 4 ;
int lineOffsetH = tileHeight / 4 ;
2024-06-26 22:18:38 +02:00
Dictionary < LandType , Color > landColorsMapping = GetLandColorsMapping ( ) ;
2024-05-10 00:03:03 +02:00
// If the classic sprite exist, use that and recolour it. Since it's fetched
// with extension, this will simply not return anything in Remastered mode.
Globals . TheTilesetManager . GetTileData ( "trans.icn" , 0 , out Tile tile ) ;
2024-06-26 22:18:38 +02:00
// -1 and 0 are used for respectively partially-filled infantry cells, and fully filled techno cells.
2024-07-02 18:39:48 +02:00
// But including them in the loop is a lot simpler than extracting the loop process into a function.
2024-06-26 22:18:38 +02:00
for ( int i = - 1 ; i < landTypes . Length ; i + + )
2023-07-06 22:48:29 +02:00
{
2024-06-26 22:18:38 +02:00
LandType landType = LandType . None ;
2024-05-10 00:03:03 +02:00
Color curCol ;
// Techno indication hijacks LandType.None just because it's in this loop.
2024-08-08 15:48:23 +02:00
bool forTechnos = i < = 0 & & ( technos ! = null | | buildings ! = null ) ;
2024-06-26 22:18:38 +02:00
bool forTechnosPart = forTechnos & & i < 0 ;
bool forTechnosFull = forTechnos & & i = = 0 ;
if ( forTechnosPart )
2023-06-22 01:23:52 +02:00
{
2024-06-26 22:18:38 +02:00
curCol = Globals . HashColorTechnoPart ;
2024-05-10 00:03:03 +02:00
}
2024-06-26 22:18:38 +02:00
else if ( forTechnosFull )
{
curCol = Globals . HashColorTechnoFull ;
}
else
{
if ( i < = 0 | | templates = = null )
{
continue ;
}
landType = landTypes [ i ] ;
if ( ! usedLandTypes . Contains ( landType ) | | ! landColorsMapping . TryGetValue ( landType , out curCol ) )
{
continue ;
}
}
// Unless it's for a placement preview, terrain types with a colour that is fully transparent are completely skipped.
if ( curCol . A = = 0 & & ! forPreview )
2024-05-10 00:03:03 +02:00
{
continue ;
}
2024-06-26 22:18:38 +02:00
curCol = Color . FromArgb ( 255 , curCol ) ;
2024-07-19 15:41:12 +02:00
string hashId = "hashing_" + tileWidth + "x" + tileHeight + "_" + ( ( uint ) curCol . ToArgb ( ) ) . ToString ( "X4" ) ;
Bitmap hashBmp = Globals . TheShapeCacheManager . GetImage ( hashId ) ;
if ( hashBmp = = null )
{
hashBmp = GenerateLinesBitmap ( tile , tileWidth , tileHeight , curCol , lineSize , lineOffsetW , lineOffsetH , graphics ) ;
Globals . TheShapeCacheManager . AddImage ( hashId , hashBmp ) ;
}
2024-05-10 00:03:03 +02:00
using ( ImageAttributes imageAttributes = new ImageAttributes ( ) )
{
2024-07-19 15:41:12 +02:00
imageAttributes . SetColorMatrix ( GetColorMatrix ( Color . White , 1.0f , soft ? 0.25f : 0.50f ) ) ;
2024-05-10 00:03:03 +02:00
for ( int y = visibleCells . Y ; y < visibleCells . Bottom ; + + y )
2023-06-23 02:35:01 +02:00
{
2024-05-10 00:03:03 +02:00
for ( int x = visibleCells . X ; x < visibleCells . Right ; + + x )
2023-06-23 02:35:01 +02:00
{
2024-06-26 22:18:38 +02:00
if ( ignoreCells ! = null & & ignoreCells . Contains ( new Point ( x , y ) ) )
{
continue ;
}
2024-08-08 15:48:23 +02:00
bool renderTerrainType = templates ! = null ;
if ( technos ! = null | | buildings ! = null )
2023-06-28 01:00:35 +02:00
{
2024-08-08 15:48:23 +02:00
ICellOccupier techno = technos ? [ y , x ] ;
ICellOccupier building = buildings ? [ y , x ] ;
bool isBuilding ;
if ( building is Building bld )
{
// Point fetch will always succeed, since the building comes from that list.
Point pt = buildings [ bld ] . Value ;
// Offset relative to building orgin point
int bldCellX = x - pt . X ;
int bldCellY = y - pt . Y ;
Size size = bld . Size ;
// Check if inside BaseOccupyMask. If not, it's just bib or extra refresh area, so ignore.
isBuilding = bldCellX < size . Width & & bldCellY < size . Height & & bld . BaseOccupyMask [ bldCellY , bldCellX ] ;
}
else
{
2024-08-12 16:18:21 +02:00
// Solid overlays in the buildings list.
2024-08-08 15:48:23 +02:00
isBuilding = building ! = null ;
}
bool isTechno = techno ! = null | | isBuilding ;
2024-08-12 16:18:21 +02:00
// Skip if it's a techno-loop and there's no techno,
// or if it's not a techno-loop and there is a techno (to avoid overlap).
if ( ( forTechnos & & ! isTechno ) | | ( ! forTechnos & & isTechno ) )
2024-05-10 00:03:03 +02:00
{
continue ;
}
2024-08-08 15:48:23 +02:00
if ( forTechnos & & isTechno )
2024-05-10 00:03:03 +02:00
{
renderTerrainType = false ;
2024-08-08 15:48:23 +02:00
bool incomplete = ! isBuilding & & techno is InfantryGroup ifg & & ifg . Infantry . Any ( inf = > inf = = null ) ;
2024-06-26 22:18:38 +02:00
if ( incomplete & & forTechnosFull | | ! incomplete & & forTechnosPart )
2024-05-10 00:03:03 +02:00
{
2024-06-26 22:18:38 +02:00
continue ;
2024-05-10 00:03:03 +02:00
}
}
2023-06-28 01:00:35 +02:00
}
2024-05-10 00:03:03 +02:00
if ( renderTerrainType )
2023-06-28 01:00:35 +02:00
{
2024-05-10 00:03:03 +02:00
Template template = templates [ y , x ] ;
LandType land = LandType . None ;
2024-06-26 22:18:38 +02:00
land = template = = null ? clearLand : template . Type . GetLandType ( template . Icon ) ;
// Only handle currently looped one
2024-05-10 00:03:03 +02:00
if ( land ! = landType )
{
continue ;
2023-07-06 22:48:29 +02:00
}
2023-06-28 01:00:35 +02:00
}
2024-07-19 15:41:12 +02:00
graphics . DrawImage ( hashBmp , new Rectangle ( tileWidth * x , tileHeight * y , tileWidth , tileHeight ) ,
0 , 0 , hashBmp . Width , hashBmp . Height , GraphicsUnit . Pixel , imageAttributes ) ;
2023-06-23 02:35:01 +02:00
}
2023-06-22 01:23:52 +02:00
}
}
}
2024-05-10 00:03:03 +02:00
}
2024-06-26 22:18:38 +02:00
private static Dictionary < LandType , Color > GetLandColorsMapping ( )
2024-05-10 00:03:03 +02:00
{
2024-06-26 22:18:38 +02:00
return new Dictionary < LandType , Color >
{
{ LandType . Clear , Globals . HashColorLandClear } , // [Clear] Normal clear terrain.
{ LandType . Beach , Globals . HashColorLandBeach } , // [Beach] Sandy beach. Can't be built on.
{ LandType . Rock , Globals . HashColorLandRock } , // [Rock] Impassable terrain.
{ LandType . Road , Globals . HashColorLandRoad } , // [Road] Units move faster on this terrain.
{ LandType . Water , Globals . HashColorLandWater } , // [Water] Ships can travel over this.
{ LandType . River , Globals . HashColorLandRiver } , // [River] Ships normally can't travel over this.
{ LandType . Rough , Globals . HashColorLandRough } , // [Rough] Rough terrain. Can't be built on
} ;
}
2024-05-10 00:03:03 +02:00
/// <summary>
/// Generates a cell filled with diagonal line hashing. If a tile is given, and it matches the requested size,
/// it used to generate the result by recolouring all its non-transparent pixels with the requested color.
/// </summary>
/// <param name="tile">Tile to use graphics from, if available.</param>
/// <param name="width">Width of the resulting image.</param>
/// <param name="height">Height of the resulting image.</param>
/// <param name="color">Color of the resulting image.</param>
/// <param name="lineSize">Thickness of the lines to paint.</param>
/// <param name="lineOffsetW">Horizontal offset between each line.</param>
/// <param name="lineOffsetH">Vertical offset between each line.</param>
/// <param name="g">Graphics object to copy render settings from.</param>
/// <returns>The image with diagonal lines, in the given color.</returns>
private static Bitmap GenerateLinesBitmap ( Tile tile , int width , int height , Color color , float lineSize , int lineOffsetW , int lineOffsetH , Graphics g )
{
2024-06-26 22:18:38 +02:00
if ( tile ! = null & & tile . Image ! = null )
2024-05-10 00:03:03 +02:00
{
byte colR = color . R ;
byte colG = color . G ;
byte colB = color . B ;
byte [ ] imgData = ImageUtils . GetImageData ( tile . Image , out int stride , PixelFormat . Format32bppArgb , true ) ;
// Replace colors, retain alpha.
for ( int i = 0 ; i < imgData . Length ; i + = 4 )
{
// Only replace non-transparent pixels
if ( imgData [ i + 3 ] = = 0 )
2023-07-27 16:38:56 +02:00
{
2024-05-10 00:03:03 +02:00
continue ;
2023-07-27 16:38:56 +02:00
}
2024-05-10 00:03:03 +02:00
// ARGB = [BB GG RR AA]
imgData [ i + 0 ] = colB ;
imgData [ i + 1 ] = colG ;
imgData [ i + 2 ] = colR ;
2023-07-06 22:48:29 +02:00
}
2024-06-26 22:18:38 +02:00
if ( tile . Image . Width = = width & & tile . Image . Height = = height )
{
return ImageUtils . BuildImage ( imgData , width , height , stride , PixelFormat . Format32bppArgb , null , null ) ;
}
else
{
using ( Bitmap tmp = ImageUtils . BuildImage ( imgData , tile . Image . Width , tile . Image . Height , stride , PixelFormat . Format32bppArgb , null , null ) )
{
Bitmap bm = new Bitmap ( width , height ) ;
using ( Graphics bmg = Graphics . FromImage ( bm ) )
{
// Pixel upscale for classic graphics
SetRenderSettings ( bmg , false ) ;
bmg . DrawImage ( tmp , new Rectangle ( 0 , 0 , width , height ) , 0 , 0 , tmp . Width , tmp . Height , GraphicsUnit . Pixel ) ;
}
return bm ;
}
}
2023-06-22 01:23:52 +02:00
}
Bitmap bitmap = new Bitmap ( width , height ) ;
int tripleWidth = width * 3 ;
int tripleHeight = height * 3 ;
bool hardLines = g ! = null & & g . InterpolationMode = = InterpolationMode . NearestNeighbor ;
int nrOfLines = 1 ;
if ( hardLines )
{
nrOfLines = Math . Max ( 1 , ( int ) Math . Round ( lineSize ) ) ;
}
using ( Bitmap img = new Bitmap ( tripleWidth , tripleHeight ) )
{
using ( Graphics gr = Graphics . FromImage ( img ) )
using ( SolidBrush sb = new SolidBrush ( color ) )
using ( Pen p = new Pen ( sb , hardLines ? 1 : lineSize ) )
{
if ( g ! = null )
{
CopyRenderSettingsFrom ( gr , g ) ;
}
int offsetX = lineOffsetW ;
int offsetY = lineOffsetH ;
int hexWidth = width * 6 ;
int hexHeight = height * 6 ;
while ( offsetX < = hexWidth & & offsetY < = hexHeight )
{
// Paint lines
for ( int i = 0 ; i < nrOfLines ; i + + )
{
gr . DrawLine ( p , offsetX + i , 0 , 0 , offsetY + i ) ;
}
offsetX + = lineOffsetW ;
offsetY + = lineOffsetH ;
}
}
using ( Graphics gr = Graphics . FromImage ( bitmap ) )
{
if ( g ! = null )
{
CopyRenderSettingsFrom ( gr , g ) ;
}
gr . DrawImage ( img , new Rectangle ( 0 , 0 , width , height ) , width , height , width , height , GraphicsUnit . Pixel ) ;
// Paint lines image on final image.
}
}
return bitmap ;
}
2024-05-10 00:03:03 +02:00
public static void SetRenderSettings ( Graphics g , bool smooth )
2022-08-11 22:49:22 +02:00
{
if ( smooth )
{
g . CompositingQuality = CompositingQuality . HighQuality ;
g . InterpolationMode = InterpolationMode . HighQualityBicubic ;
g . SmoothingMode = SmoothingMode . HighQuality ;
g . PixelOffsetMode = PixelOffsetMode . HighQuality ;
}
else
{
g . CompositingQuality = CompositingQuality . AssumeLinear ;
g . InterpolationMode = InterpolationMode . NearestNeighbor ;
g . SmoothingMode = SmoothingMode . None ;
g . PixelOffsetMode = PixelOffsetMode . Half ;
}
}
2023-06-16 21:46:44 +02:00
public static void CopyRenderSettingsFrom ( this Graphics target , Graphics source )
{
target . CompositingQuality = source . CompositingQuality ;
target . InterpolationMode = source . InterpolationMode ;
target . SmoothingMode = source . SmoothingMode ;
target . PixelOffsetMode = source . PixelOffsetMode ;
}
2022-09-22 01:26:46 +02:00
public static Rectangle RenderBounds ( Size imageSize , Size cellDimensions , Size cellSize )
2022-08-11 22:49:22 +02:00
{
2022-09-01 13:05:49 +02:00
double scaleFactorX = cellSize . Width / ( double ) Globals . OriginalTileWidth ;
double scaleFactorY = cellSize . Height / ( double ) Globals . OriginalTileHeight ;
2022-09-22 01:26:46 +02:00
return RenderBounds ( imageSize , cellDimensions , scaleFactorX , scaleFactorY ) ;
2022-08-11 22:49:22 +02:00
}
2022-09-22 01:26:46 +02:00
public static Rectangle RenderBounds ( Size imageSize , Size cellDimensions , double scaleFactor )
2022-08-11 22:49:22 +02:00
{
2022-09-22 01:26:46 +02:00
return RenderBounds ( imageSize , cellDimensions , scaleFactor , scaleFactor ) ;
2022-08-11 22:49:22 +02:00
}
2022-09-22 01:26:46 +02:00
public static Rectangle RenderBounds ( Size imageSize , Size cellDimensions , double scaleFactorX , double scaleFactorY )
2022-08-11 22:49:22 +02:00
{
Size maxSize = new Size ( cellDimensions . Width * Globals . OriginalTileWidth , cellDimensions . Height * Globals . OriginalTileHeight ) ;
// If graphics are too large, scale them down using the largest dimension
2022-09-22 01:26:46 +02:00
Size newSize = new Size ( imageSize . Width , imageSize . Height ) ;
if ( ( imageSize . Width > = imageSize . Height ) & & ( imageSize . Width > maxSize . Width ) )
2022-08-11 22:49:22 +02:00
{
2022-09-22 01:26:46 +02:00
newSize . Height = imageSize . Height * maxSize . Width / imageSize . Width ;
2022-08-11 22:49:22 +02:00
newSize . Width = maxSize . Width ;
}
2022-09-22 01:26:46 +02:00
else if ( ( imageSize . Height > = imageSize . Width ) & & ( imageSize . Height > maxSize . Height ) )
2022-08-11 22:49:22 +02:00
{
2022-09-22 01:26:46 +02:00
newSize . Width = imageSize . Width * maxSize . Height / imageSize . Height ;
2022-08-11 22:49:22 +02:00
newSize . Height = maxSize . Height ;
}
// center graphics inside bounding box
int locX = ( maxSize . Width - newSize . Width ) / 2 ;
int locY = ( maxSize . Height - newSize . Height ) / 2 ;
2023-06-21 12:18:49 +02:00
return new Rectangle ( ( int ) Math . Round ( locX * scaleFactorX ) , ( int ) Math . Round ( locY * scaleFactorY ) ,
Math . Max ( 1 , ( int ) Math . Round ( newSize . Width * scaleFactorX ) ) , Math . Max ( 1 , ( int ) Math . Round ( newSize . Height * scaleFactorY ) ) ) ;
2022-08-11 22:49:22 +02:00
}
2023-07-27 16:38:56 +02:00
2024-07-02 18:39:48 +02:00
private static Bitmap RenderTextFromSprite ( string fontsprite , TeamRemap remap , Size bounds , int [ ] shapes , bool wrap , bool cropSpacingToOne )
2024-03-28 19:22:06 +01:00
{
int nrOfChars = shapes . Length ;
if ( nrOfChars = = 0 )
{
return null ;
}
Tile [ ] tiles = new Tile [ nrOfChars ] ;
2024-07-02 18:39:48 +02:00
int [ ] offsets = new int [ nrOfChars ] ;
int [ ] widths = new int [ nrOfChars ] ;
int lineLength = wrap ? 0 : bounds . Width ;
2024-07-03 21:54:25 +02:00
int minTop = - 1 ;
2024-03-28 19:22:06 +01:00
int maxHeight = bounds . Height ;
int lineSize = 0 ;
2024-07-02 18:39:48 +02:00
// Make sure the width of the target image is at least as large as the
// largest character, to avoid endless loops on line breaks.
2024-03-28 19:22:06 +01:00
for ( int i = 0 ; i < nrOfChars ; + + i )
{
2024-07-02 18:39:48 +02:00
int charIndex = shapes [ i ] ;
if ( Globals . TheTilesetManager . GetTeamColorTileData ( fontsprite , charIndex , remap , true , out Tile character ) )
2024-03-28 19:22:06 +01:00
{
2024-07-03 21:54:25 +02:00
if ( character . Image = = null )
{
continue ;
}
2024-03-28 19:22:06 +01:00
tiles [ i ] = character ;
2024-07-02 18:39:48 +02:00
// If cropping is requested, crop character.
2024-07-03 21:54:25 +02:00
int charWidth ;
2024-07-02 18:39:48 +02:00
if ( cropSpacingToOne )
{
2024-07-03 21:54:25 +02:00
Rectangle tileBounds = character . OpaqueBounds ;
2024-07-02 18:39:48 +02:00
if ( tileBounds . Width ! = 0 )
{
offsets [ i ] = tileBounds . X ;
2024-07-03 21:54:25 +02:00
charWidth = tileBounds . Width + 1 ;
2024-07-02 18:39:48 +02:00
}
else
{
2024-07-03 21:54:25 +02:00
charWidth = character . Image . Width ;
2024-07-02 18:39:48 +02:00
}
2024-07-03 21:54:25 +02:00
minTop = minTop = = - 1 ? tileBounds . Top : Math . Min ( minTop , tileBounds . Top ) ;
maxHeight = Math . Max ( maxHeight , tileBounds . Bottom ) ;
2024-07-02 18:39:48 +02:00
}
else
{
2024-07-03 21:54:25 +02:00
charWidth = character . Image . Width ;
maxHeight = Math . Max ( maxHeight , character . Image . Height ) ;
}
if ( i = = nrOfChars - 1 )
{
charWidth - = cropSpacingToOne ? 1 : ( character . Image . Width - character . OpaqueBounds . Right ) ;
2024-07-02 18:39:48 +02:00
}
if ( wrap )
{
2024-07-03 21:54:25 +02:00
lineLength = Math . Max ( lineLength , charWidth ) ;
2024-07-02 18:39:48 +02:00
}
else
{
2024-07-03 21:54:25 +02:00
lineLength + = charWidth ;
2024-07-02 18:39:48 +02:00
}
2024-07-03 21:54:25 +02:00
widths [ i ] = charWidth ;
2024-03-28 19:22:06 +01:00
lineSize = Math . Max ( lineSize , character . Image . Height ) ;
}
}
2024-07-03 21:54:25 +02:00
minTop = Math . Max ( minTop , 0 ) ;
2024-03-28 19:22:06 +01:00
int lineHeight = 0 ;
int usedWidth = 0 ;
int curWidth = 0 ;
2024-07-03 21:54:25 +02:00
if ( lineLength = = 0 | | maxHeight - minTop = = 0 )
{
2024-08-12 16:18:21 +02:00
Bitmap bm = new Bitmap ( 2 , 2 ) ;
bm . SetResolution ( 96 , 96 ) ;
return bm ;
2024-07-03 21:54:25 +02:00
}
Bitmap bitmap = new Bitmap ( lineLength , maxHeight - minTop , PixelFormat . Format32bppArgb ) ;
2024-08-12 16:18:21 +02:00
bitmap . SetResolution ( 96 , 96 ) ;
2024-03-28 19:22:06 +01:00
using ( Graphics g = Graphics . FromImage ( bitmap ) )
{
for ( int i = 0 ; i < nrOfChars ; + + i )
{
2024-07-03 21:54:25 +02:00
Tile curChar = tiles [ i ] ;
if ( curChar = = null )
{
continue ;
}
2024-03-28 19:22:06 +01:00
usedWidth = Math . Max ( usedWidth , curWidth ) ;
2024-07-02 18:39:48 +02:00
if ( curWidth > = lineLength )
2024-03-28 19:22:06 +01:00
{
lineHeight + = lineSize ;
if ( lineHeight > = maxHeight )
{
break ;
}
usedWidth = Math . Max ( usedWidth , curWidth ) ;
curWidth = 0 ;
}
2024-07-03 21:54:25 +02:00
if ( curChar . Image = = null )
{
continue ;
}
2024-07-02 18:39:48 +02:00
int nextWidth = curWidth + widths [ i ] ;
if ( nextWidth > lineLength )
2024-03-28 19:22:06 +01:00
{
lineHeight + = lineSize ;
if ( lineHeight > = maxHeight )
{
break ;
}
curWidth = 0 ;
}
else
{
usedWidth = Math . Max ( usedWidth , nextWidth ) ;
}
2024-07-03 21:54:25 +02:00
g . DrawImage ( curChar . Image , curWidth - offsets [ i ] , lineHeight - minTop ) ;
2024-03-28 19:22:06 +01:00
curWidth = nextWidth ;
}
}
lineHeight + = lineSize ;
// Chop off if it exceeds bounds.
2024-07-02 18:39:48 +02:00
if ( ! bounds . IsEmpty & & ( lineLength > bounds . Width | | maxHeight > bounds . Height | | lineHeight < bounds . Height | | usedWidth < bounds . Width ) )
2024-03-28 19:22:06 +01:00
{
Bitmap curBm = bitmap ;
Bitmap newBm = new Bitmap ( bounds . Width , bounds . Height , PixelFormat . Format32bppArgb ) ;
int height = Math . Max ( ( bounds . Height - lineHeight ) / 2 , 0 ) ;
int width = Math . Max ( ( bounds . Width - usedWidth ) / 2 , 0 ) ;
using ( Graphics g = Graphics . FromImage ( newBm ) ) {
g . DrawImage ( curBm , width , height ) ;
}
try { curBm . Dispose ( ) ; }
catch { /* ignore */ }
bitmap = newBm ;
}
return bitmap ;
}
2023-07-27 16:38:56 +02:00
private static ColorMatrix GetColorMatrix ( Color tint , float brightnessModifier , float alphaModifier )
{
return new ColorMatrix ( new float [ ] [ ]
{
new float [ ] { tint . R * brightnessModifier / 255.0f , 0 , 0 , 0 , 0 } ,
new float [ ] { 0 , tint . G * brightnessModifier / 255.0f , 0 , 0 , 0 } ,
new float [ ] { 0 , 0 , tint . B * brightnessModifier / 255.0f , 0 , 0 } ,
new float [ ] { 0 , 0 , 0 , tint . A * alphaModifier / 255.0f , 0 } ,
new float [ ] { 0 , 0 , 0 , 0 , 1 } ,
} ) ;
}
2020-09-11 23:46:04 +03:00
}
}